mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
commit
ad5c3f0b44
95 changed files with 1647 additions and 406 deletions
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Agent.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QEventLoop>
|
||||
#include <QtCore/QStandardPaths>
|
||||
|
@ -46,14 +48,12 @@
|
|||
#include "RecordingScriptingInterface.h"
|
||||
#include "AbstractAudioInterface.h"
|
||||
|
||||
#include "Agent.h"
|
||||
#include "AvatarAudioTimer.h"
|
||||
|
||||
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
|
||||
|
||||
Agent::Agent(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_entityEditSender(),
|
||||
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
|
@ -68,7 +68,7 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
DependencyManager::set<recording::Recorder>();
|
||||
DependencyManager::set<RecordingScriptingInterface>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<ScriptEngines>();
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT);
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
||||
|
@ -143,7 +143,7 @@ void Agent::handleAudioPacket(QSharedPointer<ReceivedMessage> message) {
|
|||
_receivedAudioStream.clearBuffer();
|
||||
}
|
||||
|
||||
const QString AGENT_LOGGING_NAME = "agent";
|
||||
static const QString AGENT_LOGGING_NAME = "agent";
|
||||
|
||||
void Agent::run() {
|
||||
|
||||
|
@ -321,7 +321,7 @@ void Agent::scriptRequestFinished() {
|
|||
}
|
||||
|
||||
void Agent::executeScript() {
|
||||
_scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(_scriptContents, _payload));
|
||||
_scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload));
|
||||
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
|
||||
|
||||
// setup an Avatar for the script to use
|
||||
|
@ -376,6 +376,9 @@ void Agent::executeScript() {
|
|||
|
||||
_scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer);
|
||||
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
_scriptEngine->registerGlobalObject("Recording", recordingInterface.data());
|
||||
|
||||
// we need to make sure that init has been called for our EntityScriptingInterface
|
||||
// so that it actually has a jurisdiction listener when we ask it for it next
|
||||
entityScriptingInterface->init();
|
||||
|
|
|
@ -12,12 +12,13 @@
|
|||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include "Agent.h"
|
||||
#include "assets/AssetServer.h"
|
||||
#include "AssignmentFactory.h"
|
||||
#include "audio/AudioMixer.h"
|
||||
#include "avatars/AvatarMixer.h"
|
||||
#include "entities/EntityServer.h"
|
||||
#include "assets/AssetServer.h"
|
||||
#include "messages/MessagesMixer.h"
|
||||
#include "scripts/EntityScriptServer.h"
|
||||
|
||||
ThreadedAssignment* AssignmentFactory::unpackAssignment(ReceivedMessage& message) {
|
||||
|
||||
|
@ -39,7 +40,9 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(ReceivedMessage& message
|
|||
return new AssetServer(message);
|
||||
case Assignment::MessagesMixerType:
|
||||
return new MessagesMixer(message);
|
||||
case Assignment::EntityScriptServerType:
|
||||
return new EntityScriptServer(message);
|
||||
default:
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "NodeType.h"
|
||||
#include "SendAssetTask.h"
|
||||
#include "UploadAssetTask.h"
|
||||
#include <ClientServerUtils.h>
|
||||
|
||||
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
|
||||
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
|
||||
|
@ -190,7 +191,7 @@ void AssetServer::completeSetup() {
|
|||
cleanupUnmappedFiles();
|
||||
}
|
||||
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
||||
} else {
|
||||
qCritical() << "Asset Server assignment will not continue because mapping file could not be loaded.";
|
||||
setFinished(true);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <udt/Packet.h>
|
||||
|
||||
#include "AssetUtils.h"
|
||||
#include "ClientServerUtils.h"
|
||||
|
||||
SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
|
||||
QRunnable(),
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <NodeList.h>
|
||||
#include <NLPacketList.h>
|
||||
|
||||
#include "ClientServerUtils.h"
|
||||
|
||||
|
||||
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
|
||||
const QDir& resourcesDir) :
|
||||
|
|
|
@ -398,7 +398,7 @@ void AudioMixer::start() {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// prepare the NodeList
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
||||
nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); };
|
||||
|
||||
// parse out any AudioMixer settings
|
||||
|
|
|
@ -681,8 +681,8 @@ void AvatarMixer::run() {
|
|||
|
||||
void AvatarMixer::domainSettingsRequestComplete() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
||||
|
||||
// parse the settings to pull out the values we need
|
||||
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "AssignmentParentFinder.h"
|
||||
|
||||
#include <AvatarHashMap.h>
|
||||
|
||||
SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const {
|
||||
SpatiallyNestableWeakPointer parent;
|
||||
|
||||
|
@ -25,10 +27,21 @@ SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool&
|
|||
} else {
|
||||
parent = _tree->findEntityByEntityItemID(parentID);
|
||||
}
|
||||
if (parent.expired()) {
|
||||
success = false;
|
||||
} else {
|
||||
if (!parent.expired()) {
|
||||
success = true;
|
||||
return parent;
|
||||
}
|
||||
|
||||
// search avatars
|
||||
if (DependencyManager::isSet<AvatarHashMap>()) {
|
||||
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
|
||||
parent = avatarHashMap->getAvatarBySessionID(parentID);
|
||||
if (!parent.expired()) {
|
||||
success = true;
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
success = false;
|
||||
return parent;
|
||||
}
|
||||
|
|
|
@ -44,8 +44,7 @@ void MessagesMixer::handleMessages(QSharedPointer<ReceivedMessage> receivedMessa
|
|||
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& node)->bool {
|
||||
return node->getType() == NodeType::Agent && node->getActiveSocket() &&
|
||||
_channelSubscribers[channel].contains(node->getUUID());
|
||||
return node->getActiveSocket() && _channelSubscribers[channel].contains(node->getUUID());
|
||||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
auto packetList = MessagesClient::encodeMessagesPacket(channel, message, senderID);
|
||||
|
@ -83,5 +82,6 @@ void MessagesMixer::sendStatsPacket() {
|
|||
|
||||
void MessagesMixer::run() {
|
||||
ThreadedAssignment::commonInit(MESSAGES_MIXER_LOGGING_NAME, NodeType::MessagesMixer);
|
||||
DependencyManager::get<NodeList>()->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
}
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
||||
}
|
||||
|
|
|
@ -316,8 +316,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
int truePacketsSent = 0;
|
||||
int trueBytesSent = 0;
|
||||
int packetsSentThisInterval = 0;
|
||||
bool isFullScene = ((!viewFrustumChanged) && nodeData->getViewFrustumJustStoppedChanging())
|
||||
|| nodeData->hasLodChanged();
|
||||
bool isFullScene = nodeData->haveJSONParametersChanged() ||
|
||||
(nodeData->getUsesFrustum()
|
||||
&& ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged()));
|
||||
|
||||
bool somethingToSend = true; // assume we have something
|
||||
|
||||
|
@ -432,7 +433,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
boundaryLevelAdjust, octreeSizeScale,
|
||||
nodeData->getLastTimeBagEmpty(),
|
||||
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
|
||||
&nodeData->extraEncodeData);
|
||||
&nodeData->extraEncodeData,
|
||||
nodeData->getUsesFrustum(),
|
||||
nodeData);
|
||||
nodeData->copyCurrentViewFrustum(params.viewFrustum);
|
||||
if (viewFrustumChanged) {
|
||||
nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
|
||||
|
|
|
@ -1136,8 +1136,8 @@ void OctreeServer::domainSettingsRequestComplete() {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// we need to ask the DS about agents so we can ping/reply with them
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
|
||||
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
|
||||
|
|
372
assignment-client/src/scripts/EntityScriptServer.cpp
Normal file
372
assignment-client/src/scripts/EntityScriptServer.cpp
Normal file
|
@ -0,0 +1,372 @@
|
|||
//
|
||||
// EntityScriptServer.cpp
|
||||
// assignment-client/src/scripts
|
||||
//
|
||||
// Created by Clément Brisset on 1/5/17.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntityScriptServer.h"
|
||||
|
||||
#include <AudioConstants.h>
|
||||
#include <AudioInjectorManager.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <MessagesClient.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <ResourceManager.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <SoundCache.h>
|
||||
#include <UUID.h>
|
||||
#include <WebSocketServerClass.h>
|
||||
|
||||
#include "ClientServerUtils.h"
|
||||
#include "../entities/AssignmentParentFinder.h"
|
||||
|
||||
int EntityScriptServer::_entitiesScriptEngineCount = 0;
|
||||
|
||||
EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
ResourceManager::init();
|
||||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
|
||||
DependencyManager::set<ResourceCacheSharedItems>();
|
||||
DependencyManager::set<SoundCache>();
|
||||
DependencyManager::set<AudioInjectorManager>();
|
||||
|
||||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::ENTITY_SERVER_SCRIPT);
|
||||
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket");
|
||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
|
||||
packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
|
||||
|
||||
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
|
||||
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
|
||||
}
|
||||
|
||||
static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server";
|
||||
|
||||
void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
|
||||
// about each other.
|
||||
if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
|
||||
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
if (_entityViewer.getTree() && !_shuttingDown) {
|
||||
qDebug() << "Reloading: " << entityID;
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
checkAndCallPreload(entityID, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
|
||||
// about each other.
|
||||
if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
auto replyPacketList = NLPacketList::create(PacketType::EntityScriptGetStatusReply, QByteArray(), true, true);
|
||||
replyPacketList->writePrimitive(messageID);
|
||||
|
||||
EntityScriptDetails details;
|
||||
if (_entitiesScriptEngine->getEntityScriptDetails(entityID, details)) {
|
||||
replyPacketList->writePrimitive(true);
|
||||
replyPacketList->writePrimitive(details.status);
|
||||
replyPacketList->writeString(details.errorInfo);
|
||||
} else {
|
||||
replyPacketList->writePrimitive(false);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacketList(std::move(replyPacketList), *senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::run() {
|
||||
// make sure we request our script once the agent connects to the domain
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
ThreadedAssignment::commonInit(ENTITY_SCRIPT_SERVER_LOGGING_NAME, NodeType::EntityScriptServer);
|
||||
|
||||
// Setup MessagesClient
|
||||
auto messagesClient = DependencyManager::set<MessagesClient>();
|
||||
QThread* messagesThread = new QThread;
|
||||
messagesThread->setObjectName("Messages Client Thread");
|
||||
messagesClient->moveToThread(messagesThread);
|
||||
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
|
||||
messagesThread->start();
|
||||
|
||||
// make sure we hear about connected nodes so we can grab an ATP script if a request is pending
|
||||
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &EntityScriptServer::nodeActivated);
|
||||
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptServer::nodeKilled);
|
||||
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({
|
||||
NodeType::Agent, NodeType::AudioMixer, NodeType::AvatarMixer,
|
||||
NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer
|
||||
});
|
||||
|
||||
// Setup Script Engine
|
||||
resetEntitiesScriptEngine();
|
||||
|
||||
// we need to make sure that init has been called for our EntityScriptingInterface
|
||||
// so that it actually has a jurisdiction listener when we ask it for it next
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
entityScriptingInterface->init();
|
||||
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
||||
|
||||
_entityViewer.init();
|
||||
|
||||
// setup the JSON filter that asks for entities with a non-default serverScripts property
|
||||
QJsonObject queryJSONParameters;
|
||||
static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts";
|
||||
queryJSONParameters[SERVER_SCRIPTS_PROPERTY] = EntityQueryFilterSymbol::NonDefault;
|
||||
|
||||
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter
|
||||
_entityViewer.getOctreeQuery().setUsesFrustum(false);
|
||||
_entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
|
||||
|
||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||
|
||||
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||
|
||||
|
||||
auto tree = _entityViewer.getTree().get();
|
||||
connect(tree, &EntityTree::deletingEntity, this, &EntityScriptServer::deletingEntity, Qt::QueuedConnection);
|
||||
connect(tree, &EntityTree::addingEntity, this, &EntityScriptServer::addingEntity, Qt::QueuedConnection);
|
||||
connect(tree, &EntityTree::entityServerScriptChanging, this, &EntityScriptServer::entityServerScriptChanging, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) {
|
||||
if (activatedNode->getType() == NodeType::AudioMixer) {
|
||||
negotiateAudioFormat();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::negotiateAudioFormat() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat);
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
quint8 numberOfCodecs = (quint8)codecPlugins.size();
|
||||
negotiateFormatPacket->writePrimitive(numberOfCodecs);
|
||||
for (auto& plugin : codecPlugins) {
|
||||
auto codecName = plugin->getName();
|
||||
negotiateFormatPacket->writeString(codecName);
|
||||
}
|
||||
|
||||
// grab our audio mixer from the NodeList, if it exists
|
||||
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
|
||||
if (audioMixer) {
|
||||
// send off this mute packet
|
||||
nodeList->sendPacket(std::move(negotiateFormatPacket), *audioMixer);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) {
|
||||
QString selectedCodecName = message->readString();
|
||||
selectAudioFormat(selectedCodecName);
|
||||
}
|
||||
|
||||
void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) {
|
||||
_selectedCodecName = selectedCodecName;
|
||||
|
||||
qDebug() << "Selected Codec:" << _selectedCodecName;
|
||||
|
||||
// release any old codec encoder/decoder first...
|
||||
if (_codec && _encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
_encoder = nullptr;
|
||||
_codec = nullptr;
|
||||
}
|
||||
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
for (auto& plugin : codecPlugins) {
|
||||
if (_selectedCodecName == plugin->getName()) {
|
||||
_codec = plugin;
|
||||
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
|
||||
qDebug() << "Selected Codec Plugin:" << _codec.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::resetEntitiesScriptEngine() {
|
||||
auto engineName = QString("Entities %1").arg(++_entitiesScriptEngineCount);
|
||||
auto newEngine = QSharedPointer<ScriptEngine>(new ScriptEngine(ScriptEngine::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName));
|
||||
|
||||
auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor);
|
||||
newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
||||
|
||||
newEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
|
||||
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
|
||||
connect(newEngine.data(), &ScriptEngine::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage);
|
||||
connect(newEngine.data(), &ScriptEngine::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage);
|
||||
connect(newEngine.data(), &ScriptEngine::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage);
|
||||
connect(newEngine.data(), &ScriptEngine::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage);
|
||||
|
||||
connect(newEngine.data(), &ScriptEngine::update, this, [this] {
|
||||
_entityViewer.queryOctree();
|
||||
});
|
||||
|
||||
|
||||
newEngine->runInThread();
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngine.data());
|
||||
|
||||
_entitiesScriptEngine.swap(newEngine);
|
||||
}
|
||||
|
||||
|
||||
void EntityScriptServer::clear() {
|
||||
// unload and stop the engine
|
||||
if (_entitiesScriptEngine) {
|
||||
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
|
||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||
_entitiesScriptEngine->stop();
|
||||
}
|
||||
|
||||
// reset the engine
|
||||
if (!_shuttingDown) {
|
||||
resetEntitiesScriptEngine();
|
||||
}
|
||||
|
||||
_entityViewer.clear();
|
||||
}
|
||||
|
||||
void EntityScriptServer::shutdownScriptEngine() {
|
||||
if (_entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
|
||||
}
|
||||
_shuttingDown = true;
|
||||
|
||||
clear(); // always clear() on shutdown
|
||||
}
|
||||
|
||||
void EntityScriptServer::addingEntity(const EntityItemID& entityID) {
|
||||
checkAndCallPreload(entityID);
|
||||
}
|
||||
|
||||
void EntityScriptServer::deletingEntity(const EntityItemID& entityID) {
|
||||
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
if (_entityViewer.getTree() && !_shuttingDown) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
checkAndCallPreload(entityID, reload);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
||||
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
|
||||
|
||||
EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID);
|
||||
EntityScriptDetails details;
|
||||
bool notRunning = !_entitiesScriptEngine->getEntityScriptDetails(entityID, details);
|
||||
if (entity && (reload || notRunning || details.scriptText != entity->getServerScripts())) {
|
||||
QString scriptUrl = entity->getServerScripts();
|
||||
if (!scriptUrl.isEmpty()) {
|
||||
scriptUrl = ResourceManager::normalizeURL(scriptUrl);
|
||||
qDebug() << "Loading entity server script" << scriptUrl << "for" << entityID;
|
||||
ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
|
||||
if (!_shuttingDown && killedNode->getType() == NodeType::EntityServer) {
|
||||
if (_entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||
_entitiesScriptEngine->stop();
|
||||
}
|
||||
|
||||
resetEntitiesScriptEngine();
|
||||
|
||||
_entityViewer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::sendStatsPacket() {
|
||||
|
||||
}
|
||||
|
||||
void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
auto packetType = message->getType();
|
||||
|
||||
if (packetType == PacketType::OctreeStats) {
|
||||
|
||||
int statsMessageLength = OctreeHeadlessViewer::parseOctreeStats(message, senderNode);
|
||||
if (message->getSize() > statsMessageLength) {
|
||||
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
|
||||
int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength;
|
||||
|
||||
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
|
||||
memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader);
|
||||
|
||||
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr());
|
||||
message = QSharedPointer<ReceivedMessage>::create(*newPacket);
|
||||
} else {
|
||||
return; // bail since no piggyback data
|
||||
}
|
||||
|
||||
packetType = message->getType();
|
||||
} // fall through to piggyback message
|
||||
|
||||
if (packetType == PacketType::EntityData) {
|
||||
_entityViewer.processDatagram(*message, senderNode);
|
||||
} else if (packetType == PacketType::EntityErase) {
|
||||
_entityViewer.processEraseMessage(*message, senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
NodeType_t nodeType;
|
||||
message->peekPrimitive(&nodeType);
|
||||
|
||||
// PacketType_JURISDICTION, first byte is the node type...
|
||||
if (nodeType == NodeType::EntityServer) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->getJurisdictionListener()->
|
||||
queueReceivedPacket(message, senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::aboutToFinish() {
|
||||
shutdownScriptEngine();
|
||||
|
||||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
|
||||
|
||||
ResourceManager::cleanup();
|
||||
|
||||
// cleanup the AudioInjectorManager (and any still running injectors)
|
||||
DependencyManager::destroy<AudioInjectorManager>();
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
// cleanup codec & encoder
|
||||
if (_codec && _encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
_encoder = nullptr;
|
||||
}
|
||||
}
|
70
assignment-client/src/scripts/EntityScriptServer.h
Normal file
70
assignment-client/src/scripts/EntityScriptServer.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// EntityScriptServer.h
|
||||
// assignment-client/src/scripts
|
||||
//
|
||||
// Created by Clément Brisset on 1/5/17.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityScriptServer_h
|
||||
#define hifi_EntityScriptServer_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <EntityTreeHeadlessViewer.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
class EntityScriptServer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EntityScriptServer(ReceivedMessage& message);
|
||||
|
||||
virtual void aboutToFinish() override;
|
||||
|
||||
public slots:
|
||||
void run() override;
|
||||
void nodeActivated(SharedNodePointer activatedNode);
|
||||
void nodeKilled(SharedNodePointer killedNode);
|
||||
void sendStatsPacket() override;
|
||||
|
||||
private slots:
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
void negotiateAudioFormat();
|
||||
void selectAudioFormat(const QString& selectedCodecName);
|
||||
|
||||
void resetEntitiesScriptEngine();
|
||||
void clear();
|
||||
void shutdownScriptEngine();
|
||||
|
||||
void addingEntity(const EntityItemID& entityID);
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void entityServerScriptChanging(const EntityItemID& entityID, const bool reload);
|
||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
||||
|
||||
bool _shuttingDown { false };
|
||||
|
||||
static int _entitiesScriptEngineCount;
|
||||
QSharedPointer<ScriptEngine> _entitiesScriptEngine;
|
||||
EntityEditPacketSender _entityEditSender;
|
||||
EntityTreeHeadlessViewer _entityViewer;
|
||||
|
||||
QString _selectedCodecName;
|
||||
CodecPluginPointer _codec;
|
||||
Encoder* _encoder { nullptr };
|
||||
};
|
||||
|
||||
#endif // hifi_EntityScriptServer_h
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi5.zip
|
||||
URL_MD5 0530753e855ffc00232cc969bf1c84a8
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi6.zip
|
||||
URL_MD5 fcac808c1ba0b0f5b44ea06e2612ebab
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -1285,7 +1285,7 @@
|
|||
{
|
||||
"name": "entityScriptSourceWhitelist",
|
||||
"label": "Entity Scripts Allowed from:",
|
||||
"help": "The domains that entity scripts are allowed from. A comma separated list of domains that entity scripts are allowed from, if someone attempts to create and entity or edit an entity to have a different domain, it will be rejected. If left blank, any domain is allowed.",
|
||||
"help": "Comma separated list of URLs (with optional paths) that entity scripts are allowed from. If someone attempts to create and entity or edit an entity to have a different domain, it will be rejected. If left blank, any domain is allowed.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
|
|
|
@ -46,10 +46,9 @@ QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID
|
|||
}
|
||||
}
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::EntityServer
|
||||
<< NodeType::AssetServer
|
||||
<< NodeType::MessagesMixer;
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer
|
||||
<< NodeType::EntityScriptServer;
|
||||
|
||||
void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
if (message->getSize() == 0) {
|
||||
|
@ -72,7 +71,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
|
||||
static const NodeSet VALID_NODE_TYPES {
|
||||
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer
|
||||
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer, NodeType::EntityScriptServer
|
||||
};
|
||||
|
||||
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
|
||||
|
@ -107,7 +106,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
|
||||
if (node) {
|
||||
// set the sending sock addr and node interest set on this node
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
nodeData->setSendingSockAddr(message->getSenderSockAddr());
|
||||
|
||||
// guard against patched agents asking to hear about other agents
|
||||
|
@ -128,12 +127,12 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
emit connectedNode(node);
|
||||
} else {
|
||||
qDebug() << "Refusing connection from node at" << message->getSenderSockAddr()
|
||||
<< "with hardware address" << nodeConnection.hardwareAddress
|
||||
<< "with hardware address" << nodeConnection.hardwareAddress
|
||||
<< "and machine fingerprint" << nodeConnection.machineFingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
|
||||
const QString& hardwareAddress, const QUuid& machineFingerprint) {
|
||||
NodePermissions userPerms;
|
||||
|
||||
|
@ -283,7 +282,7 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
QString hardwareAddress;
|
||||
QUuid machineFingerprint;
|
||||
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
if (nodeData) {
|
||||
hardwareAddress = nodeData->getHardwareAddress();
|
||||
machineFingerprint = nodeData->getMachineFingerprint();
|
||||
|
@ -336,7 +335,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
// add the new node
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
// set assignment related data on the linked data for this node
|
||||
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||
|
@ -458,7 +457,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
newNode->setPermissions(userPerms);
|
||||
|
||||
// grab the linked data for our new node so we can set the username
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
// if we have a username from the connect request, set it on the DomainServerNodeData
|
||||
nodeData->setUsername(username);
|
||||
|
|
|
@ -107,7 +107,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
|
||||
|
||||
// make sure we hear about newly connected nodes from our gatekeeper
|
||||
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
||||
|
||||
|
@ -281,7 +281,7 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
|||
QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
|
||||
|
||||
qDebug() << "Reading certificate file at" << certPath << "for HTTPS.";
|
||||
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
|
||||
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
|
||||
|
||||
QFile certFile(certPath);
|
||||
certFile.open(QIODevice::ReadOnly);
|
||||
|
@ -528,12 +528,12 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket");
|
||||
|
||||
|
||||
// NodeList won't be available to the settings manager when it is created, so call registerListener here
|
||||
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::UsernameFromIDRequest, &_settingsManager, "processUsernameFromIDRequestPacket");
|
||||
|
||||
|
||||
// register the gatekeeper for the packets it needs to receive
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
|
||||
|
@ -542,7 +542,7 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
|
||||
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK");
|
||||
|
||||
|
||||
// add whatever static assignments that have been parsed to the queue
|
||||
addStaticAssignmentsToQueue();
|
||||
|
||||
|
@ -808,21 +808,19 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
for (Assignment::Type defaultedType = Assignment::AudioMixerType;
|
||||
defaultedType != Assignment::AllTypes;
|
||||
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
|
||||
if (!excludedTypes.contains(defaultedType)
|
||||
&& defaultedType != Assignment::UNUSED_1
|
||||
&& defaultedType != Assignment::AgentType) {
|
||||
|
||||
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
|
||||
|
||||
if (defaultedType == Assignment::AssetServerType) {
|
||||
// Make sure the asset-server is enabled before adding it here.
|
||||
// Initially we do not assign it by default so we can test it in HF domains first
|
||||
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
|
||||
|
||||
|
||||
if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) {
|
||||
// skip to the next iteration if asset-server isn't enabled
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// type has not been set from a command line or config file config, use the default
|
||||
// by clearing whatever exists and writing a single default assignment with no payload
|
||||
Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
|
||||
|
@ -839,9 +837,9 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
|
|||
// update this node's sockets in case they have changed
|
||||
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
|
||||
sendingNode->setLocalSocket(nodeRequestData.localSockAddr);
|
||||
|
||||
|
||||
// update the NodeInterestSet in case there have been any changes
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
|
||||
// guard against patched agents asking to hear about other agents
|
||||
auto safeInterestSet = nodeRequestData.interestList.toSet();
|
||||
|
@ -857,6 +855,44 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
|
|||
sendDomainListToNode(sendingNode, message->getSenderSockAddr());
|
||||
}
|
||||
|
||||
bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) {
|
||||
auto nodeAData = static_cast<DomainServerNodeData*>(nodeA->getLinkedData());
|
||||
auto nodeBData = static_cast<DomainServerNodeData*>(nodeB->getLinkedData());
|
||||
|
||||
// if we have no linked data for node A then B can't possibly be in the interest set
|
||||
if (!nodeAData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// first check if the general interest set A contains the type for B
|
||||
if (nodeAData->getNodeInterestSet().contains(nodeB->getType())) {
|
||||
// given that there is a match in the general interest set, do any special checks
|
||||
|
||||
// (1/19/17) Agents only need to connect to Entity Script Servers to perform administrative tasks
|
||||
// related to entity server scripts. Only agents with rez permissions should be doing that, so
|
||||
// if the agent does not have those permissions, we do not want them and the server to incur the
|
||||
// overhead of connecting to one another. Additionally we exclude agents that do not care about the
|
||||
// Entity Script Server and won't attempt to connect to it.
|
||||
|
||||
bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent
|
||||
&& nodeB->getType() == NodeType::EntityScriptServer
|
||||
&& !nodeA->getCanRez() && !nodeA->getCanRezTmp();
|
||||
|
||||
if (isAgentWithoutRights) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isScriptServerForIneffectiveAgent =
|
||||
(nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent)
|
||||
&& ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer))
|
||||
|| (!nodeB->getCanRez() && !nodeB->getCanRezTmp()));
|
||||
|
||||
return !isScriptServerForIneffectiveAgent;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int DomainServer::countConnectedUsers() {
|
||||
unsigned int result = 0;
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
@ -928,14 +964,14 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
|
|||
|
||||
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) {
|
||||
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2;
|
||||
|
||||
|
||||
// setup the extended header for the domain list packets
|
||||
// this data is at the beginning of each of the domain list packets
|
||||
QByteArray extendedHeader(NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES, 0);
|
||||
QDataStream extendedHeaderStream(&extendedHeader, QIODevice::WriteOnly);
|
||||
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
|
||||
extendedHeaderStream << limitedNodeList->getSessionUUID();
|
||||
extendedHeaderStream << node->getUUID();
|
||||
extendedHeaderStream << node->getPermissions();
|
||||
|
@ -945,7 +981,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
// always send the node their own UUID back
|
||||
QDataStream domainListStream(domainListPackets.get());
|
||||
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
// store the nodeInterestSet on this DomainServerNodeData, in case it has changed
|
||||
auto& nodeInterestSet = nodeData->getNodeInterestSet();
|
||||
|
@ -955,10 +991,8 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
// DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
|
||||
if (nodeData->isAuthenticated()) {
|
||||
// if this authenticated node has any interest types, send back those nodes as well
|
||||
limitedNodeList->eachNode([&](const SharedNodePointer& otherNode){
|
||||
if (otherNode->getUUID() != node->getUUID()
|
||||
&& nodeInterestSet.contains(otherNode->getType())) {
|
||||
|
||||
limitedNodeList->eachNode([&](const SharedNodePointer& otherNode) {
|
||||
if (otherNode->getUUID() != node->getUUID() && isInInterestSet(node, otherNode)) {
|
||||
// since we're about to add a node to the packet we start a segment
|
||||
domainListPackets->startSegment();
|
||||
|
||||
|
@ -974,7 +1008,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// send an empty list to the node, in case there were no other nodes
|
||||
domainListPackets->closeCurrentPacket(true);
|
||||
|
||||
|
@ -983,8 +1017,8 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
}
|
||||
|
||||
QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) {
|
||||
DomainServerNodeData* nodeAData = dynamic_cast<DomainServerNodeData*>(nodeA->getLinkedData());
|
||||
DomainServerNodeData* nodeBData = dynamic_cast<DomainServerNodeData*>(nodeB->getLinkedData());
|
||||
DomainServerNodeData* nodeAData = static_cast<DomainServerNodeData*>(nodeA->getLinkedData());
|
||||
DomainServerNodeData* nodeBData = static_cast<DomainServerNodeData*>(nodeB->getLinkedData());
|
||||
|
||||
if (nodeAData && nodeBData) {
|
||||
QUuid& secretUUID = nodeAData->getSessionSecretHash()[nodeB->getUUID()];
|
||||
|
@ -994,7 +1028,7 @@ QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, con
|
|||
secretUUID = QUuid::createUuid();
|
||||
|
||||
// set it on the other Node's sessionSecretHash
|
||||
reinterpret_cast<DomainServerNodeData*>(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID);
|
||||
static_cast<DomainServerNodeData*>(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID);
|
||||
}
|
||||
|
||||
return secretUUID;
|
||||
|
@ -1020,8 +1054,7 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
|
|||
[&](const SharedNodePointer& node)->bool {
|
||||
if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) {
|
||||
// is the added Node in this node's interest list?
|
||||
DomainServerNodeData* nodeData = dynamic_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
return nodeData->getNodeInterestSet().contains(addedNode->getType());
|
||||
return isInInterestSet(node, addedNode);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -1124,7 +1157,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<ReceivedMessage
|
|||
void DomainServer::setupPendingAssignmentCredits() {
|
||||
// enumerate the NodeList to find the assigned nodes
|
||||
DependencyManager::get<LimitedNodeList>()->eachNode([&](const SharedNodePointer& node){
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
|
||||
// check if we have a non-finalized transaction for this node to add this amount to
|
||||
|
@ -1510,7 +1543,7 @@ void DomainServer::sendHeartbeatToIceServer() {
|
|||
}
|
||||
|
||||
void DomainServer::processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode) {
|
||||
auto nodeData = dynamic_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
auto nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
nodeData->updateJSONStats(packetList->getMessage());
|
||||
}
|
||||
|
@ -1556,7 +1589,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
|||
nodeJson[JSON_KEY_UPTIME] = QString::number(double(QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0);
|
||||
|
||||
// if the node has pool information, add it
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
// add the node username, if it exists
|
||||
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
|
||||
|
@ -1624,23 +1657,23 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
|
||||
&& assignmentRegex.indexIn(url.path()) != -1) {
|
||||
QUuid nodeUUID = QUuid(assignmentRegex.cap(1));
|
||||
|
||||
|
||||
auto matchingNode = nodeList->nodeWithUUID(nodeUUID);
|
||||
|
||||
|
||||
// don't handle if we don't have a matching node
|
||||
if (!matchingNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nodeData = dynamic_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
|
||||
|
||||
auto nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
|
||||
// don't handle if we don't have node data for this node
|
||||
if (!nodeData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
||||
|
||||
|
||||
// check if we have an assignment that matches this temp UUID, and it is a scripted assignment
|
||||
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
|
||||
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
|
||||
|
@ -1655,7 +1688,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// request not handled
|
||||
return false;
|
||||
}
|
||||
|
@ -1687,7 +1720,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
// enumerate the NodeList to find the assigned nodes
|
||||
nodeList->eachNode([this, &assignedNodesJSON](const SharedNodePointer& node){
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (!nodeData->getAssignmentUUID().isNull()) {
|
||||
// add the node using the UUID as the key
|
||||
|
@ -1775,7 +1808,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
if (matchingNode) {
|
||||
// create a QJsonDocument with the stats QJsonObject
|
||||
QJsonObject statsObject =
|
||||
reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->getStatsJSONObject();
|
||||
static_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->getStatsJSONObject();
|
||||
|
||||
// add the node type to the JSON data for output purposes
|
||||
statsObject["node_type"] = NodeType::getNodeTypeName(matchingNode->getType()).toLower().replace(' ', '-');
|
||||
|
@ -2247,7 +2280,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
|
|||
// if the domain-server has just restarted,
|
||||
// check if there are static assignments that we need to throw into the assignment queue
|
||||
auto sharedAssignments = _allAssignments.values();
|
||||
|
||||
|
||||
// sort the assignments to put the server/mixer assignments first
|
||||
qSort(sharedAssignments.begin(), sharedAssignments.end(), [](SharedAssignmentPointer a, SharedAssignmentPointer b){
|
||||
if (a->getType() == b->getType()) {
|
||||
|
@ -2258,9 +2291,9 @@ void DomainServer::addStaticAssignmentsToQueue() {
|
|||
return a->getType() != Assignment::AgentType;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
auto staticAssignment = sharedAssignments.begin();
|
||||
|
||||
|
||||
while (staticAssignment != sharedAssignments.end()) {
|
||||
// add any of the un-matched static assignments to the queue
|
||||
|
||||
|
@ -2371,7 +2404,6 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
|
|||
}
|
||||
|
||||
void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
|
||||
auto nodeType = nodeToKill->getType();
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
const QUuid& nodeUUID = nodeToKill->getUUID();
|
||||
|
||||
|
@ -2383,10 +2415,9 @@ void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
|
|||
removedNodePacket->write(nodeUUID.toRfc4122());
|
||||
|
||||
// broadcast out the DomainServerRemovedNode message
|
||||
limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool {
|
||||
limitedNodeList->eachMatchingNode([this, &nodeToKill](const SharedNodePointer& otherNode) -> bool {
|
||||
// only send the removed node packet to nodes that care about the type of node this was
|
||||
auto nodeLinkedData = dynamic_cast<DomainServerNodeData*>(otherNode->getLinkedData());
|
||||
return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType);
|
||||
return isInInterestSet(otherNode, nodeToKill);
|
||||
}, [&limitedNodeList](const SharedNodePointer& otherNode){
|
||||
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
|
||||
});
|
||||
|
|
|
@ -132,6 +132,8 @@ private:
|
|||
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
bool isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
|
||||
|
||||
QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
|
||||
void broadcastNewNode(const SharedNodePointer& node);
|
||||
|
||||
|
|
|
@ -725,7 +725,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
|
|||
}
|
||||
|
||||
// potentially remove connect permissions for the MAC address and machine fingerprint
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
// mac address first
|
||||
NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0);
|
||||
|
@ -807,7 +807,7 @@ void DomainServerSettingsManager::processUsernameFromIDRequestPacket(QSharedPoin
|
|||
usernameFromIDReplyPacket->writeString(verifiedUsername);
|
||||
|
||||
// now put in the machine fingerprint
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid();
|
||||
usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122());
|
||||
} else {
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
#include <CursorManager.h>
|
||||
#include <DebugDraw.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <EntityScriptClient.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <ErrorDialog.h>
|
||||
#include <FileScriptingInterface.h>
|
||||
|
@ -174,6 +174,7 @@
|
|||
#include "FrameTimingsScriptingInterface.h"
|
||||
#include <GPUIdent.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
#include <EntityScriptClient.h>
|
||||
|
||||
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
// FIXME seems to be broken.
|
||||
|
@ -459,7 +460,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
// Set dependencies
|
||||
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
|
||||
DependencyManager::set<StatTracker>();
|
||||
DependencyManager::set<ScriptEngines>();
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
|
||||
DependencyManager::set<Preferences>();
|
||||
DependencyManager::set<recording::Deck>();
|
||||
DependencyManager::set<recording::Recorder>();
|
||||
|
@ -516,6 +517,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
|
||||
DependencyManager::set<CompositorHelper>();
|
||||
DependencyManager::set<OffscreenQmlSurfaceCache>();
|
||||
DependencyManager::set<EntityScriptClient>();
|
||||
return previousSessionCrashed;
|
||||
}
|
||||
|
||||
|
@ -854,7 +856,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// tell the NodeList instance who to tell the domain server we care about
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer);
|
||||
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer << NodeType::EntityScriptServer);
|
||||
|
||||
// connect to the packet sent signal of the _entityEditSender
|
||||
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
|
@ -5495,6 +5497,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Controller", scriptingInterface.data());
|
||||
UserInputMapper::registerControllerTypes(scriptEngine);
|
||||
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
scriptEngine->registerGlobalObject("Recording", recordingInterface.data());
|
||||
|
||||
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
|
||||
connect(scriptEngine, &ScriptEngine::printedMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onPrintedMessage);
|
||||
connect(scriptEngine, &ScriptEngine::errorMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onErrorMessage);
|
||||
|
|
|
@ -180,9 +180,6 @@ bool AudioInjector::injectLocally() {
|
|||
} else {
|
||||
qCDebug(audio) << "AudioInjector::injectLocally called without any data in Sound QByteArray";
|
||||
}
|
||||
|
||||
} else {
|
||||
qCDebug(audio) << "AudioInjector::injectLocally cannot inject locally with no local audio interface present.";
|
||||
}
|
||||
|
||||
return success;
|
||||
|
|
|
@ -45,9 +45,7 @@
|
|||
|
||||
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||
AbstractScriptingServicesInterface* scriptingServices) :
|
||||
OctreeRenderer(),
|
||||
_wantScripts(wantScripts),
|
||||
_entitiesScriptEngine(NULL),
|
||||
_lastPointerEventValid(false),
|
||||
_viewState(viewState),
|
||||
_scriptingServices(scriptingServices),
|
||||
|
@ -103,7 +101,7 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() {
|
|||
// Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use
|
||||
auto oldEngine = _entitiesScriptEngine;
|
||||
|
||||
auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount));
|
||||
auto newEngine = new ScriptEngine(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount));
|
||||
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(newEngine, entitiesScriptEngineDeleter);
|
||||
|
||||
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data());
|
||||
|
@ -169,7 +167,7 @@ void EntityTreeRenderer::init() {
|
|||
connect(entityTree.get(), &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity, Qt::QueuedConnection);
|
||||
connect(entityTree.get(), &EntityTree::addingEntity, this, &EntityTreeRenderer::addingEntity, Qt::QueuedConnection);
|
||||
connect(entityTree.get(), &EntityTree::entityScriptChanging,
|
||||
this, &EntityTreeRenderer::entitySciptChanging, Qt::QueuedConnection);
|
||||
this, &EntityTreeRenderer::entityScriptChanging, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::shutdown() {
|
||||
|
@ -939,7 +937,7 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) {
|
|||
}
|
||||
|
||||
|
||||
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
void EntityTreeRenderer::entityScriptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
checkAndCallPreload(entityID, reload);
|
||||
|
@ -1063,7 +1061,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
|||
}
|
||||
}
|
||||
|
||||
if (isCollisionOwner(myNodeID, entityTree, idA, collision)) {
|
||||
if (isCollisionOwner(myNodeID, entityTree, idB, collision)) {
|
||||
emit collisionWithEntity(idB, idA, collision);
|
||||
if (_entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);
|
||||
|
|
|
@ -122,7 +122,7 @@ signals:
|
|||
public slots:
|
||||
void addingEntity(const EntityItemID& entityID);
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void entitySciptChanging(const EntityItemID& entityID, const bool reload);
|
||||
void entityScriptChanging(const EntityItemID& entityID, const bool reload);
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
void updateEntityRenderStatus(bool shouldRenderEntities);
|
||||
void updateZone(const EntityItemID& id);
|
||||
|
|
|
@ -282,6 +282,8 @@ void RenderableWebEntityItem::setSourceUrl(const QString& value) {
|
|||
WebEntityItem::setSourceUrl(value);
|
||||
|
||||
if (_sourceUrl != valueBeforeSuperclassSet && _webSurface) {
|
||||
qCDebug(entities) << "Changing web entity source URL to " << _sourceUrl;
|
||||
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
|
||||
loadSourceURL();
|
||||
if (_contentType == htmlContent) {
|
||||
|
|
|
@ -121,6 +121,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_LIFETIME;
|
||||
requestedProperties += PROP_SCRIPT;
|
||||
requestedProperties += PROP_SCRIPT_TIMESTAMP;
|
||||
requestedProperties += PROP_SERVER_SCRIPTS;
|
||||
requestedProperties += PROP_COLLISION_SOUND_URL;
|
||||
requestedProperties += PROP_REGISTRATION_POINT;
|
||||
requestedProperties += PROP_ANGULAR_DAMPING;
|
||||
|
@ -265,6 +266,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, getLifetime());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, getServerScripts());
|
||||
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping());
|
||||
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible());
|
||||
|
@ -778,6 +780,19 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
|
||||
|
||||
{
|
||||
// We use this scope to work around an issue stopping server script changes
|
||||
// from being received by an entity script server running a script that continously updates an entity.
|
||||
|
||||
// Basically, we'll allow recent changes to the server scripts even if there are local changes to other properties
|
||||
// that have been made more recently.
|
||||
|
||||
bool overwriteLocalData = !ignoreServerPacket || (lastEditedFromBufferAdjusted > _serverScriptsChangedTimestamp);
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, QString, setServerScripts);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, updateRegistrationPoint);
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping);
|
||||
|
@ -1186,6 +1201,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptTimestamp, getScriptTimestamp);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(serverScripts, getServerScripts);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionSoundURL, getCollisionSoundURL);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getLocalAngularVelocity);
|
||||
|
@ -1298,6 +1314,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
// non-simulation properties below
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptTimestamp, setScriptTimestamp);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(serverScripts, setServerScripts);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
|
||||
|
@ -2262,3 +2279,30 @@ void EntityItem::globalizeProperties(EntityItemProperties& properties, const QSt
|
|||
QUuid empty;
|
||||
properties.setParentID(empty);
|
||||
}
|
||||
|
||||
|
||||
bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
|
||||
|
||||
// The intention for the query JSON filter and this method is to be flexible to handle a variety of filters for
|
||||
// ALL entity properties. Some work will need to be done to the property system so that it can be more flexible
|
||||
// (to grab the value and default value of a property given the string representation of that property, for example)
|
||||
|
||||
// currently the only property filter we handle is '+' for serverScripts
|
||||
// which means that we only handle a filtered query asking for entities where the serverScripts property is non-default
|
||||
|
||||
static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts";
|
||||
|
||||
foreach(const auto& property, jsonFilters.keys()) {
|
||||
if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) {
|
||||
// check if this entity has a non-default value for serverScripts
|
||||
if (_serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the json filter syntax did not match what we expected, return a match
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ public:
|
|||
void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); }
|
||||
quint64 getLastChangedOnServer() const { return _changedOnServer; }
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||
|
||||
virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
@ -254,12 +254,16 @@ public:
|
|||
using SpatiallyNestable::getQueryAACube;
|
||||
virtual AACube getQueryAACube(bool& success) const override;
|
||||
|
||||
const QString& getScript() const { return _script; }
|
||||
QString getScript() const { return _script; }
|
||||
void setScript(const QString& value) { _script = value; }
|
||||
|
||||
quint64 getScriptTimestamp() const { return _scriptTimestamp; }
|
||||
void setScriptTimestamp(const quint64 value) { _scriptTimestamp = value; }
|
||||
|
||||
QString getServerScripts() const { return _serverScripts; }
|
||||
void setServerScripts(const QString& serverScripts)
|
||||
{ _serverScripts = serverScripts; _serverScriptsChangedTimestamp = usecTimestampNow(); }
|
||||
|
||||
const QString& getCollisionSoundURL() const { return _collisionSoundURL; }
|
||||
void setCollisionSoundURL(const QString& value);
|
||||
|
||||
|
@ -464,6 +468,8 @@ public:
|
|||
|
||||
QUuid getLastEditedBy() const { return _lastEditedBy; }
|
||||
void setLastEditedBy(QUuid value) { _lastEditedBy = value; }
|
||||
|
||||
bool matchesJSONFilters(const QJsonObject& jsonFilters) const;
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -512,6 +518,10 @@ protected:
|
|||
QString _loadedScript; /// the value of _script when the last preload signal was sent
|
||||
quint64 _scriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP }; /// the script loaded property used for forced reload
|
||||
|
||||
QString _serverScripts;
|
||||
/// keep track of time when _serverScripts property was last changed
|
||||
quint64 _serverScriptsChangedTimestamp { ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP };
|
||||
|
||||
/// the value of _scriptTimestamp when the last preload signal was sent
|
||||
// NOTE: on construction we want this to be different from _scriptTimestamp so we intentionally bump it
|
||||
quint64 _loadedScriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP + 1 };
|
||||
|
|
|
@ -32,21 +32,21 @@ KeyLightPropertyGroup EntityItemProperties::_staticKeyLight;
|
|||
EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1);
|
||||
|
||||
EntityItemProperties::EntityItemProperties(EntityPropertyFlags desiredProperties) :
|
||||
_id(UNKNOWN_ENTITY_ID),
|
||||
_idSet(false),
|
||||
_lastEdited(0),
|
||||
_type(EntityTypes::Unknown),
|
||||
|
||||
_id(UNKNOWN_ENTITY_ID),
|
||||
_idSet(false),
|
||||
_lastEdited(0),
|
||||
_type(EntityTypes::Unknown),
|
||||
_localRenderAlpha(1.0f),
|
||||
|
||||
_localRenderAlpha(1.0f),
|
||||
_localRenderAlphaChanged(false),
|
||||
|
||||
_localRenderAlphaChanged(false),
|
||||
|
||||
_defaultSettings(true),
|
||||
_naturalDimensions(1.0f, 1.0f, 1.0f),
|
||||
_naturalPosition(0.0f, 0.0f, 0.0f),
|
||||
_desiredProperties(desiredProperties)
|
||||
_defaultSettings(true),
|
||||
_naturalDimensions(1.0f, 1.0f, 1.0f),
|
||||
_naturalPosition(0.0f, 0.0f, 0.0f),
|
||||
_desiredProperties(desiredProperties)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void EntityItemProperties::setSittingPoints(const QVector<SittingPoint>& sittingPoints) {
|
||||
|
@ -241,6 +241,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_LIFETIME, lifetime);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SERVER_SCRIPTS, serverScripts);
|
||||
CHECK_PROPERTY_CHANGE(PROP_COLLISION_SOUND_URL, collisionSoundURL);
|
||||
CHECK_PROPERTY_CHANGE(PROP_COLOR, color);
|
||||
CHECK_PROPERTY_CHANGE(PROP_COLOR_SPREAD, colorSpread);
|
||||
|
@ -388,6 +389,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIFETIME, lifetime);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT, script);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SERVER_SCRIPTS, serverScripts);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_REGISTRATION_POINT, registrationPoint);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping);
|
||||
|
@ -628,6 +630,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(lifetime, float, setLifetime);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(script, QString, setScript);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptTimestamp, quint64, setScriptTimestamp);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(serverScripts, QString, setServerScripts);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, glmVec3, setRegistrationPoint);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, glmVec3, setAngularVelocity);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(angularDamping, float, setAngularDamping);
|
||||
|
@ -917,6 +920,7 @@ QScriptValue EntityItemProperties::entityPropertyFlagsToScriptValue(QScriptEngin
|
|||
static QHash<QString, EntityPropertyList> _propertyStringsToEnums;
|
||||
|
||||
void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) {
|
||||
|
||||
static std::once_flag initMap;
|
||||
|
||||
std::call_once(initMap, [](){
|
||||
|
@ -934,6 +938,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_PROPERTY_TO_MAP(PROP_LIFETIME, Lifetime, lifetime, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SCRIPT, Script, script, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor);
|
||||
|
@ -1201,6 +1206,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, properties.getLifetime());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, properties.getScript());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, properties.getScriptTimestamp());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, properties.getServerScripts());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, properties.getRegistrationPoint());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, properties.getAngularVelocity());
|
||||
|
@ -1501,6 +1507,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT, QString, setScript);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SERVER_SCRIPTS, QString, setServerScripts);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity);
|
||||
|
@ -1627,7 +1634,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
|
||||
// NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE
|
||||
// when encoding/decoding edits because otherwise they can't polymorph to other shape types
|
||||
if (properties.getType() == EntityTypes::Shape ||
|
||||
if (properties.getType() == EntityTypes::Shape ||
|
||||
properties.getType() == EntityTypes::Box ||
|
||||
properties.getType() == EntityTypes::Sphere) {
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape);
|
||||
|
@ -1688,6 +1695,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_userDataChanged = true;
|
||||
_scriptChanged = true;
|
||||
_scriptTimestampChanged = true;
|
||||
_serverScriptsChanged = true;
|
||||
_collisionSoundURLChanged = true;
|
||||
_registrationPointChanged = true;
|
||||
_angularVelocityChanged = true;
|
||||
|
@ -1896,6 +1904,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (scriptTimestampChanged()) {
|
||||
out += "scriptTimestamp";
|
||||
}
|
||||
if (serverScriptsChanged()) {
|
||||
out += "serverScripts";
|
||||
}
|
||||
if (collisionSoundURLChanged()) {
|
||||
out += "collisionSoundURL";
|
||||
}
|
||||
|
|
|
@ -221,6 +221,8 @@ public:
|
|||
|
||||
DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY);
|
||||
|
||||
DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS);
|
||||
|
||||
static QString getBackgroundModeString(BackgroundMode mode);
|
||||
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ const bool ENTITY_ITEM_DEFAULT_VISIBLE = true;
|
|||
|
||||
const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString("");
|
||||
const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0;
|
||||
const QString ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS = QString("");
|
||||
const QString ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL = QString("");
|
||||
const glm::vec3 ENTITY_ITEM_DEFAULT_REGISTRATION_POINT = ENTITY_ITEM_HALF_VEC3; // center
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include "../octree/OctreeQueryNode.h"
|
||||
#include <OctreeQueryNode.h>
|
||||
|
||||
class EntityNodeData : public OctreeQueryNode {
|
||||
public:
|
||||
|
@ -22,9 +22,15 @@ public:
|
|||
|
||||
quint64 getLastDeletedEntitiesSentAt() const { return _lastDeletedEntitiesSentAt; }
|
||||
void setLastDeletedEntitiesSentAt(quint64 sentAt) { _lastDeletedEntitiesSentAt = sentAt; }
|
||||
|
||||
// these can only be called from the OctreeSendThread for the given Node
|
||||
void insertEntitySentLastFrame(const QUuid& entityID) { _entitiesSentLastFrame.insert(entityID); }
|
||||
void removeEntitySentLastFrame(const QUuid& entityID) { _entitiesSentLastFrame.remove(entityID); }
|
||||
bool sentEntityLastFrame(const QUuid& entityID) { return _entitiesSentLastFrame.contains(entityID); }
|
||||
|
||||
private:
|
||||
quint64 _lastDeletedEntitiesSentAt { usecTimestampNow() };
|
||||
QSet<QUuid> _entitiesSentLastFrame;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityNodeData_h
|
|
@ -183,6 +183,8 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_LAST_EDITED_BY,
|
||||
|
||||
PROP_SERVER_SCRIPTS,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
#include "SimulationOwner.h"
|
||||
#include "ZoneEntityItem.h"
|
||||
#include "WebEntityItem.h"
|
||||
#include <EntityScriptClient.h>
|
||||
|
||||
|
||||
EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) :
|
||||
_entityTree(NULL),
|
||||
|
@ -670,6 +672,38 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
|
|||
return result;
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) {
|
||||
auto client = DependencyManager::get<EntityScriptClient>();
|
||||
return client->reloadServerScript(entityID);
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) {
|
||||
auto client = DependencyManager::get<EntityScriptClient>();
|
||||
auto request = client->createScriptStatusRequest(entityID);
|
||||
connect(request, &GetScriptStatusRequest::finished, callback.engine(), [callback](GetScriptStatusRequest* request) mutable {
|
||||
QString statusString;
|
||||
switch (request->getStatus()) {
|
||||
case RUNNING:
|
||||
statusString = "running";
|
||||
break;
|
||||
case ERROR_LOADING_SCRIPT:
|
||||
statusString = "error_loading_script";
|
||||
break;
|
||||
case ERROR_RUNNING_SCRIPT:
|
||||
statusString = "error_running_script";
|
||||
break;
|
||||
default:
|
||||
statusString = "";
|
||||
break;
|
||||
}
|
||||
QScriptValueList args { request->getResponseReceived(), request->getIsRunning(), statusString, request->getErrorInfo() };
|
||||
callback.call(QScriptValue(), args);
|
||||
request->deleteLater();
|
||||
});
|
||||
request->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntityScriptingInterface::setLightsArePickable(bool value) {
|
||||
LightEntityItem::setLightsArePickable(value);
|
||||
}
|
||||
|
|
|
@ -210,6 +210,9 @@ public slots:
|
|||
/// order to return an accurate result
|
||||
Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue());
|
||||
|
||||
Q_INVOKABLE bool reloadServerScripts(QUuid entityID);
|
||||
Q_INVOKABLE bool getServerScriptStatus(QUuid entityID, QScriptValue callback);
|
||||
|
||||
Q_INVOKABLE void setLightsArePickable(bool value);
|
||||
Q_INVOKABLE bool getLightsArePickable() const;
|
||||
|
||||
|
|
|
@ -390,10 +390,14 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
|
|||
return result;
|
||||
}
|
||||
|
||||
void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID, const bool reload) {
|
||||
void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID, bool reload) {
|
||||
emit entityScriptChanging(entityItemID, reload);
|
||||
}
|
||||
|
||||
void EntityTree::emitEntityServerScriptChanging(const EntityItemID& entityItemID, bool reload) {
|
||||
emit entityServerScriptChanging(entityItemID, reload);
|
||||
}
|
||||
|
||||
void EntityTree::notifyNewCollisionSoundURL(const QString& newURL, const EntityItemID& entityID) {
|
||||
emit newCollisionSoundURL(QUrl(newURL), entityID);
|
||||
}
|
||||
|
@ -958,9 +962,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
|
||||
if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
|
||||
bool passedWhiteList = false;
|
||||
auto entityScript = properties.getScript();
|
||||
|
||||
// grab a URL representation of the entity script so we can check the host for this script
|
||||
auto entityScriptURL = QUrl::fromUserInput(properties.getScript());
|
||||
|
||||
for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) {
|
||||
if (entityScript.startsWith(whiteListedPrefix, Qt::CaseInsensitive)) {
|
||||
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
|
||||
|
||||
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
|
||||
if (entityScriptURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
|
||||
entityScriptURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
|
||||
passedWhiteList = true;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ using ModelWeakPointer = std::weak_ptr<Model>;
|
|||
|
||||
class EntitySimulation;
|
||||
|
||||
namespace EntityQueryFilterSymbol {
|
||||
static const QString NonDefault = "+";
|
||||
}
|
||||
|
||||
class NewlyCreatedEntityHook {
|
||||
public:
|
||||
|
@ -201,7 +204,8 @@ public:
|
|||
|
||||
void entityChanged(EntityItemPointer entity);
|
||||
|
||||
void emitEntityScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
||||
void emitEntityScriptChanging(const EntityItemID& entityItemID, bool reload);
|
||||
void emitEntityServerScriptChanging(const EntityItemID& entityItemID, bool reload);
|
||||
|
||||
void setSimulation(EntitySimulationPointer simulation);
|
||||
EntitySimulationPointer getSimulation() const { return _simulation; }
|
||||
|
@ -270,6 +274,7 @@ signals:
|
|||
void deletingEntity(const EntityItemID& entityID);
|
||||
void addingEntity(const EntityItemID& entityID);
|
||||
void entityScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
||||
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
||||
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
|
||||
void clearingEntities();
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <OctreeUtils.h>
|
||||
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityNodeData.h"
|
||||
#include "EntityItemProperties.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntityTreeElement.h"
|
||||
|
@ -94,7 +95,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
|
|||
bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const {
|
||||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
|
||||
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||
|
@ -231,7 +232,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
|
|||
}
|
||||
|
||||
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
|
||||
EncodeBitstreamParams& params) const {
|
||||
EncodeBitstreamParams& params) const {
|
||||
|
||||
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
|
||||
|
||||
|
@ -278,25 +279,57 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
int numberOfEntitiesOffset = 0;
|
||||
withReadLock([&] {
|
||||
QVector<uint16_t> indexesOfEntitiesToInclude;
|
||||
|
||||
|
||||
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
|
||||
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
||||
// need to handle the case where our sibling elements need encoding but we don't.
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted) {
|
||||
|
||||
QJsonObject jsonFilters;
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
|
||||
|
||||
if (entityNodeData) {
|
||||
// we have an EntityNodeData instance
|
||||
// so we should assume that means we might have JSON filters to check
|
||||
jsonFilters = entityNodeData->getJSONParameters();
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < _entityItems.size(); i++) {
|
||||
EntityItemPointer entity = _entityItems[i];
|
||||
bool includeThisEntity = true;
|
||||
|
||||
if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastViewFrustumSent) {
|
||||
if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastQuerySent) {
|
||||
includeThisEntity = false;
|
||||
}
|
||||
|
||||
if (hadElementExtraData) {
|
||||
includeThisEntity = includeThisEntity &&
|
||||
entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID());
|
||||
// if this entity has been updated since our last full send and there are json filters, check them
|
||||
if (includeThisEntity && !jsonFilters.isEmpty()) {
|
||||
|
||||
// if params include JSON filters, check if this entity matches
|
||||
bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
|
||||
|
||||
if (entityMatchesFilters) {
|
||||
// make sure this entity is in the set of entities sent last frame
|
||||
entityNodeData->insertEntitySentLastFrame(entity->getID());
|
||||
|
||||
} else {
|
||||
// we might include this entity if it matched in the previous frame
|
||||
if (entityNodeData->sentEntityLastFrame(entity->getID())) {
|
||||
|
||||
entityNodeData->removeEntitySentLastFrame(entity->getID());
|
||||
} else {
|
||||
includeThisEntity = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeThisEntity || params.recurseEverything) {
|
||||
if (includeThisEntity && hadElementExtraData) {
|
||||
includeThisEntity = entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID());
|
||||
}
|
||||
|
||||
// we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum
|
||||
// which can sometimes not be the case when JSON filters are sent
|
||||
if (params.usesFrustum && (includeThisEntity || params.recurseEverything)) {
|
||||
|
||||
// we want to use the maximum possible box for this, so that we don't have to worry about the nuance of
|
||||
// simulation changing what's visible. consider the case where the entity contains an angular velocity
|
||||
|
@ -925,6 +958,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
// 3) remember the old cube for the entity so we can mark it as dirty
|
||||
if (entityItem) {
|
||||
QString entityScriptBefore = entityItem->getScript();
|
||||
QString entityServerScriptsBefore = entityItem->getServerScripts();
|
||||
quint64 entityScriptTimestampBefore = entityItem->getScriptTimestamp();
|
||||
bool bestFitBefore = bestFitEntityBounds(entityItem);
|
||||
EntityTreeElementPointer currentContainingElement = _myTree->getContainingElement(entityItemID);
|
||||
|
@ -948,6 +982,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
}
|
||||
|
||||
QString entityScriptAfter = entityItem->getScript();
|
||||
QString entityServerScriptsAfter = entityItem->getServerScripts();
|
||||
quint64 entityScriptTimestampAfter = entityItem->getScriptTimestamp();
|
||||
bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter;
|
||||
|
||||
|
@ -956,6 +991,9 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
if (entityScriptBefore != entityScriptAfter || reload) {
|
||||
_myTree->emitEntityScriptChanging(entityItemID, reload); // the entity script has changed
|
||||
}
|
||||
if (entityServerScriptsBefore != entityServerScriptsAfter || reload) {
|
||||
_myTree->emitEntityServerScriptChanging(entityItemID, reload); // the entity server script has changed
|
||||
}
|
||||
|
||||
} else {
|
||||
entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args);
|
||||
|
|
|
@ -127,7 +127,6 @@ public:
|
|||
|
||||
bool alreadyFullyEncoded(EncodeBitstreamParams& params) const;
|
||||
|
||||
|
||||
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
|
||||
virtual OctreeElement::AppendState appendElementData(OctreePacketData* packetData,
|
||||
EncodeBitstreamParams& params) const override;
|
||||
|
|
|
@ -174,7 +174,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_IS_SPOTLIGHT;
|
||||
|
|
|
@ -126,7 +126,7 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_COLOR;
|
||||
|
|
|
@ -26,7 +26,7 @@ class LineEntityItem : public EntityItem {
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -160,7 +160,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -469,7 +469,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_COLOR;
|
||||
|
|
|
@ -26,7 +26,7 @@ class PolyLineEntityItem : public EntityItem {
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -179,7 +179,7 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_VOXEL_VOLUME_SIZE;
|
||||
|
|
|
@ -26,7 +26,7 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -137,7 +137,7 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_SHAPE;
|
||||
|
|
|
@ -99,7 +99,7 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_TEXT;
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -84,7 +84,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_SOURCE_URL;
|
||||
|
@ -129,7 +129,6 @@ void WebEntityItem::setSourceUrl(const QString& value) {
|
|||
|
||||
if (newURL.isValid()) {
|
||||
_sourceUrl = newURL.toDisplayString();
|
||||
qCDebug(entities) << "Changed web entity source URL to " << _sourceUrl;
|
||||
} else {
|
||||
qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL.";
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -133,7 +133,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
// TODO: eventually only include properties changed since the params.lastQuerySent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -21,11 +21,10 @@
|
|||
#include <DependencyManager.h>
|
||||
|
||||
#include "AssetUtils.h"
|
||||
#include "ClientServerUtils.h"
|
||||
#include "LimitedNodeList.h"
|
||||
#include "NLPacket.h"
|
||||
#include "Node.h"
|
||||
#include "ReceivedMessage.h"
|
||||
#include "ResourceCache.h"
|
||||
|
||||
class GetMappingRequest;
|
||||
class SetMappingRequest;
|
||||
|
@ -60,8 +59,6 @@ public:
|
|||
Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
|
||||
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
|
||||
|
||||
static const MessageID INVALID_MESSAGE_ID = 0;
|
||||
|
||||
public slots:
|
||||
void init();
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ void AssetRequest::start() {
|
|||
_assetInfoRequestID = assetClient->getAssetInfo(_hash,
|
||||
[this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
|
||||
|
||||
_assetInfoRequestID = AssetClient::INVALID_MESSAGE_ID;
|
||||
_assetInfoRequestID = INVALID_MESSAGE_ID;
|
||||
|
||||
_info = info;
|
||||
|
||||
|
@ -119,7 +119,7 @@ void AssetRequest::start() {
|
|||
// If the request is dead, return
|
||||
return;
|
||||
}
|
||||
_assetRequestID = AssetClient::INVALID_MESSAGE_ID;
|
||||
_assetRequestID = INVALID_MESSAGE_ID;
|
||||
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
|
|
|
@ -64,8 +64,8 @@ private:
|
|||
QString _hash;
|
||||
QByteArray _data;
|
||||
int _numPendingRequests { 0 };
|
||||
MessageID _assetRequestID { AssetClient::INVALID_MESSAGE_ID };
|
||||
MessageID _assetInfoRequestID { AssetClient::INVALID_MESSAGE_ID };
|
||||
MessageID _assetRequestID { INVALID_MESSAGE_ID };
|
||||
MessageID _assetInfoRequestID { INVALID_MESSAGE_ID };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
using MessageID = uint32_t;
|
||||
using DataOffset = int64_t;
|
||||
|
||||
using AssetPath = QString;
|
||||
|
@ -64,4 +63,4 @@ bool isValidFilePath(const AssetPath& path);
|
|||
bool isValidPath(const AssetPath& path);
|
||||
bool isValidHash(const QString& hashString);
|
||||
|
||||
#endif
|
||||
#endif // hifi_AssetUtils_h
|
||||
|
|
|
@ -35,6 +35,8 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) {
|
|||
return Assignment::AssetServerType;
|
||||
case NodeType::MessagesMixer:
|
||||
return Assignment::MessagesMixerType;
|
||||
case NodeType::EntityScriptServer:
|
||||
return Assignment::EntityScriptServerType;
|
||||
default:
|
||||
return Assignment::AllTypes;
|
||||
}
|
||||
|
@ -139,6 +141,8 @@ const char* Assignment::getTypeName() const {
|
|||
return "entity-server";
|
||||
case Assignment::MessagesMixerType:
|
||||
return "messages-mixer";
|
||||
case Assignment::EntityScriptServerType:
|
||||
return "entity-script-server";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
AgentType = 2,
|
||||
AssetServerType = 3,
|
||||
MessagesMixerType = 4,
|
||||
UNUSED_1 = 5,
|
||||
EntityScriptServerType = 5,
|
||||
EntityServerType = 6,
|
||||
AllTypes = 7
|
||||
};
|
||||
|
|
21
libraries/networking/src/ClientServerUtils.h
Normal file
21
libraries/networking/src/ClientServerUtils.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
//
|
||||
// ClientServerUtils.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Ryan Huffman on 2017/01/20
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ClientServerUtils_h
|
||||
#define hifi_ClientServerUtils_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
using MessageID = uint32_t;
|
||||
const MessageID INVALID_MESSAGE_ID = 0;
|
||||
|
||||
#endif // hifi_ClientServerUtils_h
|
165
libraries/networking/src/EntityScriptClient.cpp
Normal file
165
libraries/networking/src/EntityScriptClient.cpp
Normal file
|
@ -0,0 +1,165 @@
|
|||
#include "EntityScriptClient.h"
|
||||
#include "NodeList.h"
|
||||
#include "NetworkLogging.h"
|
||||
#include "EntityScriptUtils.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
MessageID EntityScriptClient::_currentID = 0;
|
||||
|
||||
GetScriptStatusRequest::GetScriptStatusRequest(QUuid entityID) : _entityID(entityID) {
|
||||
}
|
||||
|
||||
GetScriptStatusRequest::~GetScriptStatusRequest() {
|
||||
|
||||
}
|
||||
|
||||
void GetScriptStatusRequest::start() {
|
||||
auto client = DependencyManager::get<EntityScriptClient>();
|
||||
client->getEntityServerScriptStatus(_entityID, [this](bool responseReceived, bool isRunning, EntityScriptStatus status, QString errorInfo) {
|
||||
_responseReceived = responseReceived;
|
||||
_isRunning = isRunning;
|
||||
_status = status;
|
||||
_errorInfo = errorInfo;
|
||||
|
||||
emit finished(this);
|
||||
});
|
||||
}
|
||||
|
||||
EntityScriptClient::EntityScriptClient() {
|
||||
setCustomDeleter([](Dependency* dependency){
|
||||
static_cast<EntityScriptClient*>(dependency)->deleteLater();
|
||||
});
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply, this, "handleGetScriptStatusReply");
|
||||
|
||||
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptClient::handleNodeKilled);
|
||||
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,
|
||||
this, &EntityScriptClient::handleNodeClientConnectionReset);
|
||||
}
|
||||
|
||||
GetScriptStatusRequest* EntityScriptClient::createScriptStatusRequest(QUuid entityID) {
|
||||
auto request = new GetScriptStatusRequest(entityID);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
bool EntityScriptClient::reloadServerScript(QUuid entityID) {
|
||||
// Send packet to entity script server
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer entityScriptServer = nodeList->soloNodeOfType(NodeType::EntityScriptServer);
|
||||
|
||||
if (entityScriptServer) {
|
||||
auto id = entityID.toRfc4122();
|
||||
auto payloadSize = id.size();
|
||||
auto packet = NLPacket::create(PacketType::ReloadEntityServerScript, payloadSize, true);
|
||||
|
||||
packet->write(id);
|
||||
|
||||
if (nodeList->sendPacket(std::move(packet), *entityScriptServer) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MessageID EntityScriptClient::getEntityServerScriptStatus(QUuid entityID, GetScriptStatusCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer entityScriptServer = nodeList->soloNodeOfType(NodeType::EntityScriptServer);
|
||||
|
||||
if (entityScriptServer) {
|
||||
auto packetList = NLPacketList::create(PacketType::EntityScriptGetStatus, QByteArray(), true, true);
|
||||
|
||||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->write(entityID.toRfc4122());
|
||||
|
||||
if (nodeList->sendPacketList(std::move(packetList), *entityScriptServer) != -1) {
|
||||
_pendingEntityScriptStatusRequests[entityScriptServer][messageID] = callback;
|
||||
|
||||
return messageID;
|
||||
}
|
||||
}
|
||||
|
||||
callback(false, false, ERROR_LOADING_SCRIPT, "");
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
void EntityScriptClient::handleGetScriptStatusReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
MessageID messageID;
|
||||
bool isKnown { false };
|
||||
EntityScriptStatus status = ERROR_LOADING_SCRIPT;
|
||||
QString errorInfo { "" };
|
||||
|
||||
message->readPrimitive(&messageID);
|
||||
message->readPrimitive(&isKnown);
|
||||
|
||||
if (isKnown) {
|
||||
message->readPrimitive(&status);
|
||||
errorInfo = message->readString();
|
||||
}
|
||||
|
||||
// Check if we have any pending requests for this node
|
||||
auto messageMapIt = _pendingEntityScriptStatusRequests.find(senderNode);
|
||||
if (messageMapIt != _pendingEntityScriptStatusRequests.end()) {
|
||||
|
||||
// Found the node, get the MessageID -> Callback map
|
||||
auto& messageCallbackMap = messageMapIt->second;
|
||||
|
||||
// Check if we have this pending request
|
||||
auto requestIt = messageCallbackMap.find(messageID);
|
||||
if (requestIt != messageCallbackMap.end()) {
|
||||
auto callback = requestIt->second;
|
||||
callback(true, isKnown, status, errorInfo);
|
||||
messageCallbackMap.erase(requestIt);
|
||||
}
|
||||
|
||||
// Although the messageCallbackMap may now be empty, we won't delete the node until we have disconnected from
|
||||
// it to avoid constantly creating/deleting the map on subsequent requests.
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptClient::handleNodeKilled(SharedNodePointer node) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
if (node->getType() != NodeType::EntityScriptServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
forceFailureOfPendingRequests(node);
|
||||
}
|
||||
|
||||
void EntityScriptClient::handleNodeClientConnectionReset(SharedNodePointer node) {
|
||||
// a client connection to a Node was reset
|
||||
// if it was an EntityScriptServer we need to cause anything pending to fail so it is re-attempted
|
||||
|
||||
if (node->getType() != NodeType::EntityScriptServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
//qCDebug(entity_script_client) << "EntityScriptClient detected client connection reset handshake with Asset Server - failing any pending requests";
|
||||
|
||||
forceFailureOfPendingRequests(node);
|
||||
}
|
||||
|
||||
void EntityScriptClient::forceFailureOfPendingRequests(SharedNodePointer node) {
|
||||
|
||||
{
|
||||
auto messageMapIt = _pendingEntityScriptStatusRequests.find(node);
|
||||
if (messageMapIt != _pendingEntityScriptStatusRequests.end()) {
|
||||
for (const auto& value : messageMapIt->second) {
|
||||
value.second(false, false, ERROR_LOADING_SCRIPT, "");
|
||||
}
|
||||
messageMapIt->second.clear();
|
||||
}
|
||||
}
|
||||
}
|
75
libraries/networking/src/EntityScriptClient.h
Normal file
75
libraries/networking/src/EntityScriptClient.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// EntityScriptClient.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Ryan Huffman on 2017/01/13
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityScriptClient_h
|
||||
#define hifi_EntityScriptClient_h
|
||||
|
||||
#include "ClientServerUtils.h"
|
||||
#include "LimitedNodeList.h"
|
||||
#include "ReceivedMessage.h"
|
||||
#include "AssetUtils.h"
|
||||
#include "EntityScriptUtils.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <unordered_map>
|
||||
|
||||
using GetScriptStatusCallback = std::function<void(bool responseReceived, bool isRunning, EntityScriptStatus status, QString errorInfo)>;
|
||||
|
||||
class GetScriptStatusRequest : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GetScriptStatusRequest(QUuid);
|
||||
~GetScriptStatusRequest();
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
bool getResponseReceived() const { return _responseReceived; }
|
||||
bool getIsRunning() const { return _isRunning; }
|
||||
EntityScriptStatus getStatus() const { return _status; }
|
||||
QString getErrorInfo() const { return _errorInfo; }
|
||||
|
||||
signals:
|
||||
void finished(GetScriptStatusRequest* request);
|
||||
|
||||
private:
|
||||
QUuid _entityID;
|
||||
MessageID _messageID;
|
||||
|
||||
bool _responseReceived;
|
||||
bool _isRunning;
|
||||
EntityScriptStatus _status;
|
||||
QString _errorInfo;
|
||||
};
|
||||
|
||||
class EntityScriptClient : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityScriptClient();
|
||||
|
||||
Q_INVOKABLE GetScriptStatusRequest* createScriptStatusRequest(QUuid entityID);
|
||||
|
||||
bool reloadServerScript(QUuid entityID);
|
||||
MessageID getEntityServerScriptStatus(QUuid entityID, GetScriptStatusCallback callback);
|
||||
|
||||
private slots:
|
||||
void handleNodeKilled(SharedNodePointer node);
|
||||
void handleNodeClientConnectionReset(SharedNodePointer node);
|
||||
|
||||
void handleGetScriptStatusReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
static MessageID _currentID;
|
||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetScriptStatusCallback>> _pendingEntityScriptStatusRequests;
|
||||
|
||||
void forceFailureOfPendingRequests(SharedNodePointer node);
|
||||
};
|
||||
|
||||
#endif
|
21
libraries/networking/src/EntityScriptUtils.h
Normal file
21
libraries/networking/src/EntityScriptUtils.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// EntityScriptUtils.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Ryan Huffman on 2017/01/13
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityScriptUtils_h
|
||||
#define hifi_EntityScriptUtils_h
|
||||
|
||||
enum EntityScriptStatus {
|
||||
ERROR_LOADING_SCRIPT,
|
||||
ERROR_RUNNING_SCRIPT,
|
||||
RUNNING
|
||||
};
|
||||
|
||||
#endif // hifi_EntityScriptUtils_h
|
|
@ -68,7 +68,7 @@ void GetMappingRequest::doStart() {
|
|||
_mappingRequestID = assetClient->getAssetMapping(_path,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
|
@ -100,7 +100,7 @@ void GetAllMappingsRequest::doStart() {
|
|||
_mappingRequestID = assetClient->getAllAssetMappings(
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
|
@ -152,7 +152,7 @@ void SetMappingRequest::doStart() {
|
|||
_mappingRequestID = assetClient->setAssetMapping(_path, _hash,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
|
@ -195,7 +195,7 @@ void DeleteMappingsRequest::doStart() {
|
|||
_mappingRequestID = assetClient->deleteAssetMappings(_paths,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
|
@ -237,7 +237,7 @@ void RenameMappingRequest::doStart() {
|
|||
_mappingRequestID = assetClient->renameAssetMapping(_oldPath, _newPath,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
|
||||
protected:
|
||||
Error _error { NoError };
|
||||
MessageID _mappingRequestID { AssetClient::INVALID_MESSAGE_ID };
|
||||
MessageID _mappingRequestID { INVALID_MESSAGE_ID };
|
||||
|
||||
private:
|
||||
virtual void doStart() = 0;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
Q_DECLARE_LOGGING_CATEGORY(resourceLog)
|
||||
Q_DECLARE_LOGGING_CATEGORY(networking)
|
||||
Q_DECLARE_LOGGING_CATEGORY(asset_client)
|
||||
Q_DECLARE_LOGGING_CATEGORY(entity_script_client)
|
||||
Q_DECLARE_LOGGING_CATEGORY(messages_client)
|
||||
|
||||
#endif // hifi_NetworkLogging_h
|
||||
|
|
|
@ -41,6 +41,7 @@ void NodeType::init() {
|
|||
TypeNameHash.insert(NodeType::AvatarMixer, "Avatar Mixer");
|
||||
TypeNameHash.insert(NodeType::MessagesMixer, "Messages Mixer");
|
||||
TypeNameHash.insert(NodeType::AssetServer, "Asset Server");
|
||||
TypeNameHash.insert(NodeType::EntityScriptServer, "Entity Script Server");
|
||||
TypeNameHash.insert(NodeType::Unassigned, "Unassigned");
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace NodeType {
|
|||
const NodeType_t AvatarMixer = 'W';
|
||||
const NodeType_t AssetServer = 'A';
|
||||
const NodeType_t MessagesMixer = 'm';
|
||||
const NodeType_t EntityScriptServer = 'S';
|
||||
const NodeType_t Unassigned = 1;
|
||||
|
||||
void init();
|
||||
|
|
|
@ -48,7 +48,9 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
return VERSION_ENTITIES_LAST_EDITED_BY;
|
||||
return VERSION_ENTITIES_SERVER_SCRIPTS;
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::JsonFilter);
|
||||
case PacketType::AvatarIdentity:
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
|
|
|
@ -107,7 +107,10 @@ public:
|
|||
RequestsDomainListData,
|
||||
ExitingSpaceBubble,
|
||||
PerAvatarGainSet,
|
||||
LAST_PACKET_TYPE = PerAvatarGainSet
|
||||
EntityScriptGetStatus,
|
||||
EntityScriptGetStatusReply,
|
||||
ReloadEntityServerScript,
|
||||
LAST_PACKET_TYPE = ReloadEntityServerScript
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -197,6 +200,11 @@ const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62;
|
|||
const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63;
|
||||
const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64;
|
||||
const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65;
|
||||
const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
|
||||
|
||||
enum class EntityQueryPacketVersion: PacketVersion {
|
||||
JsonFilter = 18
|
||||
};
|
||||
|
||||
enum class AssetServerPacketVersion: PacketVersion {
|
||||
VegasCongestionControl = 19
|
||||
|
|
|
@ -899,7 +899,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
}
|
||||
|
||||
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
|
||||
if (!params.recurseEverything && !element->isInView(params.viewFrustum)) {
|
||||
if (params.usesFrustum && !params.recurseEverything && !element->isInView(params.viewFrustum)) {
|
||||
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
@ -1015,7 +1015,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
|
||||
ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
||||
if (!params.recurseEverything) {
|
||||
if (params.usesFrustum && !params.recurseEverything) {
|
||||
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
|
||||
|
@ -1077,7 +1077,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// If we were previously in the view, then we normally will return out of here and stop recursing. But
|
||||
// if we're in deltaView mode, and this element has changed since it was last sent, then we do
|
||||
// need to send it.
|
||||
if (wasInView && !(params.deltaView && element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) {
|
||||
if (wasInView && !(params.deltaView && element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedWasInView(element);
|
||||
}
|
||||
|
@ -1088,7 +1088,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
|
||||
// then we can also bail early and save bits
|
||||
if (!params.forceSendScene && !params.deltaView &&
|
||||
!element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) {
|
||||
!element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE)) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedNoChange(element);
|
||||
}
|
||||
|
@ -1176,7 +1176,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
int originalIndex = indexOfChildren[i];
|
||||
|
||||
bool childIsInView = (childElement &&
|
||||
(params.recurseEverything ||
|
||||
(params.recurseEverything || !params.usesFrustum ||
|
||||
(nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are
|
||||
(nodeLocationThisView == ViewFrustum::INTERSECT &&
|
||||
childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view
|
||||
|
@ -1189,7 +1189,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
} else {
|
||||
// Before we consider this further, let's see if it's in our LOD scope...
|
||||
float boundaryDistance = params.recurseEverything ? 1 :
|
||||
float boundaryDistance = params.recurseEverything || !params.usesFrustum ? 1 :
|
||||
boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
|
||||
|
@ -1211,7 +1211,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
|
||||
bool childIsOccluded = false; // assume it's not occluded
|
||||
|
||||
bool shouldRender = params.recurseEverything ||
|
||||
bool shouldRender = params.recurseEverything || !params.usesFrustum ||
|
||||
childElement->calculateShouldRender(params.viewFrustum,
|
||||
params.octreeElementSizeScale, params.boundaryLevelAdjust);
|
||||
|
||||
|
@ -1247,7 +1247,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// need to send it.
|
||||
if (!childWasInView ||
|
||||
(params.deltaView &&
|
||||
childElement->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){
|
||||
childElement->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))){
|
||||
|
||||
childrenDataBits += (1 << (7 - originalIndex));
|
||||
inViewWithColorCount++;
|
||||
|
@ -1451,7 +1451,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep
|
||||
// recursing, by returning TRUE in recurseChildrenWithData().
|
||||
|
||||
if (params.recurseEverything || recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) {
|
||||
if (params.recurseEverything || !params.usesFrustum
|
||||
|| recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) {
|
||||
|
||||
// Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this
|
||||
// will be true. But if the tree has already been encoded, we will skip this.
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
#include <shared/ReadWriteLockable.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
|
@ -68,7 +69,7 @@ class EncodeBitstreamParams {
|
|||
public:
|
||||
ViewFrustum viewFrustum;
|
||||
ViewFrustum lastViewFrustum;
|
||||
quint64 lastViewFrustumSent;
|
||||
quint64 lastQuerySent;
|
||||
int maxEncodeLevel;
|
||||
int maxLevelReached;
|
||||
bool includeExistsBits;
|
||||
|
@ -81,6 +82,8 @@ public:
|
|||
OctreeSceneStats* stats;
|
||||
JurisdictionMap* jurisdictionMap;
|
||||
OctreeElementExtraEncodeData* extraEncodeData;
|
||||
bool usesFrustum;
|
||||
NodeData* nodeData;
|
||||
|
||||
// output hints from the encode process
|
||||
typedef enum {
|
||||
|
@ -104,12 +107,14 @@ public:
|
|||
bool useDeltaView = false,
|
||||
int boundaryLevelAdjust = NO_BOUNDARY_ADJUST,
|
||||
float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
quint64 lastViewFrustumSent = IGNORE_LAST_SENT,
|
||||
quint64 lastQuerySent = IGNORE_LAST_SENT,
|
||||
bool forceSendScene = true,
|
||||
OctreeSceneStats* stats = IGNORE_SCENE_STATS,
|
||||
JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP,
|
||||
OctreeElementExtraEncodeData* extraEncodeData = NULL) :
|
||||
lastViewFrustumSent(lastViewFrustumSent),
|
||||
OctreeElementExtraEncodeData* extraEncodeData = nullptr,
|
||||
bool usesFrustum = true,
|
||||
NodeData* nodeData = nullptr) :
|
||||
lastQuerySent(lastQuerySent),
|
||||
maxEncodeLevel(maxEncodeLevel),
|
||||
maxLevelReached(0),
|
||||
includeExistsBits(includeExistsBits),
|
||||
|
@ -121,6 +126,8 @@ public:
|
|||
stats(stats),
|
||||
jurisdictionMap(jurisdictionMap),
|
||||
extraEncodeData(extraEncodeData),
|
||||
usesFrustum(usesFrustum),
|
||||
nodeData(nodeData),
|
||||
stopReason(UNKNOWN)
|
||||
{
|
||||
lastViewFrustum.invalidate();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#ifndef hifi_OctreeConstants_h
|
||||
#define hifi_OctreeConstants_h
|
||||
|
||||
#include <QtGlobal> // for quint64
|
||||
#include <QtCore/QString> // for quint64/QString
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
const quint64 CHANGE_FUDGE = 1000 * 200; // useconds of fudge in determining if we want to resend changed voxels
|
||||
|
|
|
@ -108,7 +108,7 @@ public:
|
|||
virtual bool isRendered() const { return getShouldRender(); }
|
||||
|
||||
virtual bool deleteApproved() const { return true; }
|
||||
|
||||
|
||||
virtual bool canRayIntersect() const { return isLeaf(); }
|
||||
/// \param center center of sphere in meters
|
||||
/// \param radius radius of sphere in meters
|
||||
|
|
|
@ -36,6 +36,8 @@ public:
|
|||
virtual void render(RenderArgs* renderArgs) override { /* swallow these */ }
|
||||
|
||||
void setJurisdictionListener(JurisdictionListener* jurisdictionListener) { _jurisdictionListener = jurisdictionListener; }
|
||||
|
||||
OctreeQuery& getOctreeQuery() { return _octreeQuery; }
|
||||
|
||||
static int parseOctreeStats(QSharedPointer<ReceivedMessage> message, SharedNodePointer sourceNode);
|
||||
static void trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket);
|
||||
|
|
|
@ -9,13 +9,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeQuery.h"
|
||||
|
||||
|
||||
OctreeQuery::OctreeQuery() {
|
||||
_maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
}
|
||||
|
@ -23,35 +24,27 @@ OctreeQuery::OctreeQuery() {
|
|||
int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
||||
unsigned char* bufferStart = destinationBuffer;
|
||||
|
||||
// TODO: DRY this up to a shared method
|
||||
// that can pack any type given the number of bytes
|
||||
// and return the number of bytes to push the pointer
|
||||
// back a boolean (cut to 1 byte) to designate if this query uses the sent view frustum
|
||||
memcpy(destinationBuffer, &_usesFrustum, sizeof(_usesFrustum));
|
||||
destinationBuffer += sizeof(_usesFrustum);
|
||||
|
||||
if (_usesFrustum) {
|
||||
// TODO: DRY this up to a shared method
|
||||
// that can pack any type given the number of bytes
|
||||
// and return the number of bytes to push the pointer
|
||||
|
||||
// camera details
|
||||
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
|
||||
destinationBuffer += sizeof(_cameraPosition);
|
||||
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation);
|
||||
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov);
|
||||
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip);
|
||||
memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition));
|
||||
destinationBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
}
|
||||
|
||||
// camera details
|
||||
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
|
||||
destinationBuffer += sizeof(_cameraPosition);
|
||||
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation);
|
||||
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov);
|
||||
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip);
|
||||
memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition));
|
||||
destinationBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
|
||||
// bitMask of less than byte wide items
|
||||
unsigned char bitItems = 0;
|
||||
|
||||
// NOTE: we need to keep these here for new clients to talk to old servers. After we know that the clients and
|
||||
// servers and clients have all been updated we could remove these bits. New servers will always force these
|
||||
// features on old clients even if they don't ask for them. (which old clients will properly handle). New clients
|
||||
// will always ask for these so that old servers will use these features.
|
||||
setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT);
|
||||
setAtBit(bitItems, WANT_COLOR_AT_BIT);
|
||||
setAtBit(bitItems, WANT_DELTA_AT_BIT);
|
||||
setAtBit(bitItems, WANT_COMPRESSION);
|
||||
|
||||
*destinationBuffer++ = bitItems;
|
||||
|
||||
// desired Max Octree PPS
|
||||
memcpy(destinationBuffer, &_maxQueryPPS, sizeof(_maxQueryPPS));
|
||||
destinationBuffer += sizeof(_maxQueryPPS);
|
||||
|
@ -67,6 +60,25 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius));
|
||||
destinationBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
// create a QByteArray that holds the binary representation of the JSON parameters
|
||||
QByteArray binaryParametersDocument;
|
||||
|
||||
if (!_jsonParameters.isEmpty()) {
|
||||
binaryParametersDocument = QJsonDocument(_jsonParameters).toBinaryData();
|
||||
}
|
||||
|
||||
// write the size of the JSON parameters
|
||||
uint16_t binaryParametersBytes = binaryParametersDocument.size();
|
||||
memcpy(destinationBuffer, &binaryParametersBytes, sizeof(binaryParametersBytes));
|
||||
destinationBuffer += sizeof(binaryParametersBytes);
|
||||
|
||||
// pack the binary JSON parameters
|
||||
// NOTE: for now we assume that the filters that will be set are all small enough that we will not have a packet > MTU
|
||||
if (binaryParametersDocument.size() > 0) {
|
||||
memcpy(destinationBuffer, binaryParametersDocument.data(), binaryParametersBytes);
|
||||
destinationBuffer += binaryParametersBytes;
|
||||
}
|
||||
|
||||
return destinationBuffer - bufferStart;
|
||||
}
|
||||
|
||||
|
@ -76,24 +88,22 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(message.getRawMessage());
|
||||
const unsigned char* sourceBuffer = startPosition;
|
||||
|
||||
// camera details
|
||||
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition));
|
||||
sourceBuffer += sizeof(_cameraPosition);
|
||||
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation);
|
||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov);
|
||||
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip);
|
||||
memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition));
|
||||
sourceBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
|
||||
// optional feature flags
|
||||
unsigned char bitItems = 0;
|
||||
bitItems = (unsigned char)*sourceBuffer++;
|
||||
|
||||
// NOTE: we used to use these bits to set feature request items if we need to extend the protocol with optional features
|
||||
// do it here with... wantFeature= oneAtBit(bitItems, WANT_FEATURE_BIT);
|
||||
Q_UNUSED(bitItems);
|
||||
// check if this query uses a view frustum
|
||||
memcpy(&_usesFrustum, sourceBuffer, sizeof(_usesFrustum));
|
||||
sourceBuffer += sizeof(_usesFrustum);
|
||||
|
||||
if (_usesFrustum) {
|
||||
// unpack camera details
|
||||
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition));
|
||||
sourceBuffer += sizeof(_cameraPosition);
|
||||
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation);
|
||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov);
|
||||
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip);
|
||||
memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition));
|
||||
sourceBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
}
|
||||
|
||||
// desired Max Octree PPS
|
||||
memcpy(&_maxQueryPPS, sourceBuffer, sizeof(_maxQueryPPS));
|
||||
|
@ -106,13 +116,28 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
// desired boundaryLevelAdjust
|
||||
memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust));
|
||||
sourceBuffer += sizeof(_boundaryLevelAdjust);
|
||||
|
||||
auto bytesRead = sourceBuffer - startPosition;
|
||||
auto bytesLeft = message.getSize() - bytesRead;
|
||||
if (bytesLeft >= (int)sizeof(_cameraCenterRadius)) {
|
||||
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
|
||||
sourceBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
|
||||
sourceBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
// check if we have a packed JSON filter
|
||||
uint16_t binaryParametersBytes;
|
||||
memcpy(&binaryParametersBytes, sourceBuffer, sizeof(binaryParametersBytes));
|
||||
sourceBuffer += sizeof(binaryParametersBytes);
|
||||
|
||||
if (binaryParametersBytes > 0) {
|
||||
// unpack the binary JSON parameters
|
||||
QByteArray binaryJSONParameters { binaryParametersBytes, 0 };
|
||||
memcpy(binaryJSONParameters.data(), sourceBuffer, binaryParametersBytes);
|
||||
sourceBuffer += binaryParametersBytes;
|
||||
|
||||
// grab the parameter object from the packed binary representation of JSON
|
||||
auto newJsonDocument = QJsonDocument::fromBinaryData(binaryJSONParameters);
|
||||
|
||||
QWriteLocker jsonParameterLocker { &_jsonParametersLock };
|
||||
_jsonParameters = newJsonDocument.object();
|
||||
}
|
||||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,14 +31,11 @@ typedef unsigned long long quint64;
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
|
||||
#include <NodeData.h>
|
||||
|
||||
// First bitset
|
||||
const int WANT_LOW_RES_MOVING_BIT = 0;
|
||||
const int WANT_COLOR_AT_BIT = 1;
|
||||
const int WANT_DELTA_AT_BIT = 2;
|
||||
const int UNUSED_BIT_3 = 3; // unused... available for new feature
|
||||
const int WANT_COMPRESSION = 4; // 5th bit
|
||||
|
||||
class OctreeQuery : public NodeData {
|
||||
Q_OBJECT
|
||||
|
@ -71,11 +68,19 @@ public:
|
|||
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
|
||||
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
|
||||
void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; }
|
||||
|
||||
|
||||
// getters/setters for JSON filter
|
||||
QJsonObject getJSONParameters() { QReadLocker locker { &_jsonParametersLock }; return _jsonParameters; }
|
||||
void setJSONParameters(const QJsonObject& jsonParameters)
|
||||
{ QWriteLocker locker { &_jsonParametersLock }; _jsonParameters = jsonParameters; }
|
||||
|
||||
// related to Octree Sending strategies
|
||||
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
|
||||
float getOctreeSizeScale() const { return _octreeElementSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
||||
bool getUsesFrustum() { return _usesFrustum; }
|
||||
void setUsesFrustum(bool usesFrustum) { _usesFrustum = usesFrustum; }
|
||||
|
||||
public slots:
|
||||
void setMaxQueryPacketsPerSecond(int maxQueryPPS) { _maxQueryPPS = maxQueryPPS; }
|
||||
|
@ -97,7 +102,12 @@ protected:
|
|||
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations
|
||||
int _boundaryLevelAdjust = 0; /// used for LOD calculations
|
||||
|
||||
|
||||
uint8_t _usesFrustum = true;
|
||||
|
||||
QJsonObject _jsonParameters;
|
||||
QReadWriteLock _jsonParametersLock;
|
||||
|
||||
private:
|
||||
// privatize the copy constructor and assignment operator so they cannot be called
|
||||
OctreeQuery(const OctreeQuery&);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// OctreeQueryNode.cpp
|
||||
// assignment-client/src/octree
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Stephen Birarda on 3/21/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
|
@ -18,7 +18,6 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "OctreeSendThread.h"
|
||||
|
||||
void OctreeQueryNode::nodeKilled() {
|
||||
_isShuttingDown = true;
|
||||
|
@ -156,65 +155,71 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
if (_isShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool currentViewFrustumChanged = false;
|
||||
ViewFrustum newestViewFrustum;
|
||||
// get position and orientation details from the camera
|
||||
newestViewFrustum.setPosition(getCameraPosition());
|
||||
newestViewFrustum.setOrientation(getCameraOrientation());
|
||||
|
||||
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
|
||||
|
||||
// Also make sure it's got the correct lens details from the camera
|
||||
float originalFOV = getCameraFov();
|
||||
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
|
||||
|
||||
if (0.0f != getCameraAspectRatio() &&
|
||||
0.0f != getCameraNearClip() &&
|
||||
0.0f != getCameraFarClip() &&
|
||||
getCameraNearClip() != getCameraFarClip()) {
|
||||
newestViewFrustum.setProjection(glm::perspective(
|
||||
glm::radians(wideFOV), // hack
|
||||
getCameraAspectRatio(),
|
||||
getCameraNearClip(),
|
||||
getCameraFarClip()));
|
||||
}
|
||||
|
||||
|
||||
{ // if there has been a change, then recalculate
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
||||
_currentViewFrustum = newestViewFrustum;
|
||||
_currentViewFrustum.calculate();
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for LOD changes from the client
|
||||
if (_lodInitialized) {
|
||||
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = true;
|
||||
}
|
||||
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lodChanged = true;
|
||||
}
|
||||
|
||||
if (!_usesFrustum) {
|
||||
// this client does not use a view frustum so the view frustum for this query has not changed
|
||||
return false;
|
||||
} else {
|
||||
_lodInitialized = true;
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = false;
|
||||
bool currentViewFrustumChanged = false;
|
||||
|
||||
ViewFrustum newestViewFrustum;
|
||||
// get position and orientation details from the camera
|
||||
newestViewFrustum.setPosition(getCameraPosition());
|
||||
newestViewFrustum.setOrientation(getCameraOrientation());
|
||||
|
||||
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
|
||||
|
||||
// Also make sure it's got the correct lens details from the camera
|
||||
float originalFOV = getCameraFov();
|
||||
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
|
||||
|
||||
if (0.0f != getCameraAspectRatio() &&
|
||||
0.0f != getCameraNearClip() &&
|
||||
0.0f != getCameraFarClip() &&
|
||||
getCameraNearClip() != getCameraFarClip()) {
|
||||
newestViewFrustum.setProjection(glm::perspective(
|
||||
glm::radians(wideFOV), // hack
|
||||
getCameraAspectRatio(),
|
||||
getCameraNearClip(),
|
||||
getCameraFarClip()));
|
||||
}
|
||||
|
||||
|
||||
{ // if there has been a change, then recalculate
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
||||
_currentViewFrustum = newestViewFrustum;
|
||||
_currentViewFrustum.calculate();
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for LOD changes from the client
|
||||
if (_lodInitialized) {
|
||||
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = true;
|
||||
}
|
||||
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lodChanged = true;
|
||||
}
|
||||
} else {
|
||||
_lodInitialized = true;
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = false;
|
||||
}
|
||||
|
||||
// When we first detect that the view stopped changing, we record this.
|
||||
// but we don't change it back to false until we've completely sent this
|
||||
// scene.
|
||||
if (_viewFrustumChanging && !currentViewFrustumChanged) {
|
||||
_viewFrustumJustStoppedChanging = true;
|
||||
}
|
||||
_viewFrustumChanging = currentViewFrustumChanged;
|
||||
return currentViewFrustumChanged;
|
||||
}
|
||||
|
||||
// When we first detect that the view stopped changing, we record this.
|
||||
// but we don't change it back to false until we've completely sent this
|
||||
// scene.
|
||||
if (_viewFrustumChanging && !currentViewFrustumChanged) {
|
||||
_viewFrustumJustStoppedChanging = true;
|
||||
}
|
||||
_viewFrustumChanging = currentViewFrustumChanged;
|
||||
return currentViewFrustumChanged;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::setViewSent(bool viewSent) {
|
||||
|
@ -315,3 +320,15 @@ void OctreeQueryNode::parseNackPacket(ReceivedMessage& message) {
|
|||
_nackedSequenceNumbers.enqueue(sequenceNumber);
|
||||
}
|
||||
}
|
||||
|
||||
bool OctreeQueryNode::haveJSONParametersChanged() {
|
||||
bool parametersChanged = false;
|
||||
auto currentParameters = getJSONParameters();
|
||||
|
||||
if (_lastCheckJSONParameters != currentParameters) {
|
||||
parametersChanged = true;
|
||||
_lastCheckJSONParameters = currentParameters;
|
||||
}
|
||||
|
||||
return parametersChanged;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// OctreeQueryNode.h
|
||||
// assignment-client/src/octree
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/4/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
|
@ -15,11 +15,11 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <NodeData.h>
|
||||
#include <OctreeConstants.h>
|
||||
#include <OctreeElementBag.h>
|
||||
#include <OctreePacketData.h>
|
||||
#include <OctreeQuery.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeElementBag.h"
|
||||
#include "OctreePacketData.h"
|
||||
#include "OctreeQuery.h"
|
||||
#include "OctreeSceneStats.h"
|
||||
#include "SentPacketHistory.h"
|
||||
#include <qqueue.h>
|
||||
|
||||
|
@ -100,6 +100,9 @@ public:
|
|||
bool hasNextNackedPacket() const;
|
||||
const NLPacket* getNextNackedPacket();
|
||||
|
||||
// call only from OctreeSendThread for the given node
|
||||
bool haveJSONParametersChanged();
|
||||
|
||||
private:
|
||||
OctreeQueryNode(const OctreeQueryNode &);
|
||||
OctreeQueryNode& operator= (const OctreeQueryNode&);
|
||||
|
@ -143,6 +146,8 @@ private:
|
|||
quint64 _sceneSendStartTime = 0;
|
||||
|
||||
std::array<char, udt::MAX_PACKET_SIZE> _lastOctreePayload;
|
||||
|
||||
QJsonObject _lastCheckJSONParameters;
|
||||
};
|
||||
|
||||
#endif // hifi_OctreeQueryNode_h
|
|
@ -27,7 +27,7 @@ class PhysicalEntitySimulation;
|
|||
using PhysicalEntitySimulationPointer = std::shared_ptr<PhysicalEntitySimulation>;
|
||||
using SetOfEntityMotionStates = QSet<EntityMotionState*>;
|
||||
|
||||
class PhysicalEntitySimulation :public EntitySimulation {
|
||||
class PhysicalEntitySimulation : public EntitySimulation {
|
||||
public:
|
||||
PhysicalEntitySimulation();
|
||||
~PhysicalEntitySimulation();
|
||||
|
|
|
@ -43,14 +43,14 @@ public:
|
|||
class SteamScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool isRunning READ isRunning)
|
||||
Q_PROPERTY(bool running READ isRunning)
|
||||
|
||||
public:
|
||||
SteamScriptingInterface(QObject* parent, SteamClientPlugin* plugin) : QObject(parent) {}
|
||||
SteamScriptingInterface(QObject* parent, SteamClientPlugin* plugin) : QObject(parent), _plugin(plugin) {}
|
||||
|
||||
public slots:
|
||||
bool isRunning() const { return _plugin->isRunning(); }
|
||||
void openInviteOverlay() const { _plugin->openInviteOverlay(); }
|
||||
public slots:
|
||||
bool isRunning() const { return _plugin && _plugin->isRunning(); }
|
||||
void openInviteOverlay() const { if (_plugin) { _plugin->openInviteOverlay(); } }
|
||||
|
||||
private:
|
||||
SteamClientPlugin* _plugin;
|
||||
|
|
|
@ -148,25 +148,29 @@ static bool hasCorrectSyntax(const QScriptProgram& program, ScriptEngine* report
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName, ScriptEngine* reportingEngine) {
|
||||
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName, ScriptEngine* reportingEngine, QString* exceptionMessage = nullptr) {
|
||||
if (engine.hasUncaughtException()) {
|
||||
const auto backtrace = engine.uncaughtExceptionBacktrace();
|
||||
const auto exception = engine.uncaughtException().toString();
|
||||
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
|
||||
engine.clearExceptions();
|
||||
|
||||
auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
|
||||
QString message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
|
||||
if (!backtrace.empty()) {
|
||||
static const auto lineSeparator = "\n ";
|
||||
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
|
||||
}
|
||||
reportingEngine->scriptErrorMessage(qPrintable(message));
|
||||
if (exceptionMessage) {
|
||||
*exceptionMessage = message;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString) :
|
||||
ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const QString& fileNameString) :
|
||||
_context(context),
|
||||
_scriptContents(scriptContents),
|
||||
_timerFunctionMap(),
|
||||
_fileNameString(fileNameString),
|
||||
|
@ -181,6 +185,22 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
|||
setProcessEventsInterval(MSECS_PER_SECOND);
|
||||
}
|
||||
|
||||
QString ScriptEngine::getContext() const {
|
||||
switch (_context) {
|
||||
case CLIENT_SCRIPT:
|
||||
return "client";
|
||||
case ENTITY_CLIENT_SCRIPT:
|
||||
return "entity_client";
|
||||
case ENTITY_SERVER_SCRIPT:
|
||||
return "entity_server";
|
||||
case AGENT_SCRIPT:
|
||||
return "agent";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
ScriptEngine::~ScriptEngine() {
|
||||
scriptInfoMessage("Script Engine shutting down:" + getFilename());
|
||||
|
||||
|
@ -563,11 +583,7 @@ void ScriptEngine::init() {
|
|||
// constants
|
||||
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
|
||||
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
registerGlobalObject("Recording", recordingInterface.data());
|
||||
|
||||
registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
|
||||
registerGlobalObject("Assets", &_assetScriptingInterface);
|
||||
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
|
||||
}
|
||||
|
@ -1377,6 +1393,15 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
|
|||
}
|
||||
}
|
||||
|
||||
bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const {
|
||||
auto it = _entityScripts.constFind(entityID);
|
||||
if (it == _entityScripts.constEnd()) {
|
||||
return false;
|
||||
}
|
||||
details = it.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
// since all of these operations can be asynch we will always do the actual work in the response handler
|
||||
// for the download
|
||||
void ScriptEngine::loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
|
||||
|
@ -1424,11 +1449,24 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
bool isFileUrl = isURL && scriptOrURL.startsWith("file://");
|
||||
auto fileName = isURL ? scriptOrURL : "EmbeddedEntityScript";
|
||||
|
||||
EntityScriptDetails newDetails;
|
||||
newDetails.scriptText = scriptOrURL;
|
||||
|
||||
if (!success) {
|
||||
newDetails.status = ERROR_LOADING_SCRIPT;
|
||||
newDetails.errorInfo = "Failed to load script";
|
||||
_entityScripts[entityID] = newDetails;
|
||||
return;
|
||||
}
|
||||
|
||||
QScriptProgram program(contents, fileName);
|
||||
if (!hasCorrectSyntax(program, this)) {
|
||||
if (!isFileUrl) {
|
||||
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||
}
|
||||
newDetails.status = ERROR_RUNNING_SCRIPT;
|
||||
newDetails.errorInfo = "Bad syntax";
|
||||
_entityScripts[entityID] = newDetails;
|
||||
return; // done processing script
|
||||
}
|
||||
|
||||
|
@ -1453,7 +1491,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
});
|
||||
testConstructor = sandbox.evaluate(program);
|
||||
}
|
||||
if (hadUncaughtExceptions(sandbox, program.fileName(), this)) {
|
||||
|
||||
QString exceptionMessage;
|
||||
if (hadUncaughtExceptions(sandbox, program.fileName(), this, &exceptionMessage)) {
|
||||
newDetails.status = ERROR_RUNNING_SCRIPT;
|
||||
newDetails.errorInfo = exceptionMessage;
|
||||
_entityScripts[entityID] = newDetails;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1476,6 +1520,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||
}
|
||||
|
||||
newDetails.status = ERROR_RUNNING_SCRIPT;
|
||||
newDetails.errorInfo = "Could not find constructor";
|
||||
_entityScripts[entityID] = newDetails;
|
||||
|
||||
return; // done processing script
|
||||
}
|
||||
|
||||
|
@ -1492,8 +1540,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
};
|
||||
doWithEnvironment(entityID, sandboxURL, initialization);
|
||||
|
||||
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL };
|
||||
newDetails.scriptObject = entityScriptObject;
|
||||
newDetails.lastModified = lastModified;
|
||||
newDetails.definingSandboxURL = sandboxURL;
|
||||
_entityScripts[entityID] = newDetails;
|
||||
|
||||
if (isURL) {
|
||||
setParentURL("");
|
||||
}
|
||||
|
@ -1519,7 +1570,9 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
|||
#endif
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
if (_entityScripts[entityID].status == RUNNING) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
}
|
||||
_entityScripts.remove(entityID);
|
||||
stopAllTimersForEntityScript(entityID);
|
||||
}
|
||||
|
@ -1538,7 +1591,9 @@ void ScriptEngine::unloadAllEntityScripts() {
|
|||
qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]";
|
||||
#endif
|
||||
foreach(const EntityItemID& entityID, _entityScripts.keys()) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
if (_entityScripts[entityID].status == RUNNING) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
}
|
||||
}
|
||||
_entityScripts.clear();
|
||||
|
||||
|
@ -1577,7 +1632,7 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
|
|||
QString scriptContents = QTextStream(&file).readAll();
|
||||
this->unloadEntityScript(entityID);
|
||||
this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true);
|
||||
if (!_entityScripts.contains(entityID)) {
|
||||
if (!_entityScripts.contains(entityID) || _entityScripts[entityID].status != RUNNING) {
|
||||
scriptWarningMessage("Reload script " + details.scriptText + " failed");
|
||||
} else {
|
||||
details = _entityScripts[entityID];
|
||||
|
@ -1636,7 +1691,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
#endif
|
||||
|
||||
refreshFileScript(entityID);
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
|
@ -1668,7 +1723,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
#endif
|
||||
|
||||
refreshFileScript(entityID);
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
|
@ -1701,7 +1756,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
#endif
|
||||
|
||||
refreshFileScript(entityID);
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <LimitedNodeList.h>
|
||||
#include <EntityItemID.h>
|
||||
#include <EntitiesScriptEngineProvider.h>
|
||||
#include <EntityScriptUtils.h>
|
||||
|
||||
#include "PointerEvent.h"
|
||||
#include "ArrayBufferClass.h"
|
||||
|
@ -58,16 +59,30 @@ typedef QHash<QString, CallbackList> RegisteredEventHandlers;
|
|||
|
||||
class EntityScriptDetails {
|
||||
public:
|
||||
QString scriptText;
|
||||
QScriptValue scriptObject;
|
||||
int64_t lastModified;
|
||||
QUrl definingSandboxURL;
|
||||
EntityScriptStatus status { RUNNING };
|
||||
|
||||
// If status indicates an error, this contains a human-readable string giving more information about the error.
|
||||
QString errorInfo { "" };
|
||||
|
||||
QString scriptText { "" };
|
||||
QScriptValue scriptObject { QScriptValue() };
|
||||
int64_t lastModified { 0 };
|
||||
QUrl definingSandboxURL { QUrl() };
|
||||
};
|
||||
|
||||
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString context READ getContext)
|
||||
public:
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""));
|
||||
|
||||
enum Context {
|
||||
CLIENT_SCRIPT,
|
||||
ENTITY_CLIENT_SCRIPT,
|
||||
ENTITY_SERVER_SCRIPT,
|
||||
AGENT_SCRIPT
|
||||
};
|
||||
|
||||
ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""));
|
||||
~ScriptEngine();
|
||||
|
||||
/// run the script in a dedicated thread. This will have the side effect of evalulating
|
||||
|
@ -118,6 +133,12 @@ public:
|
|||
/// to scripts. we may not need this to be invokable
|
||||
void loadURL(const QUrl& scriptURL, bool reload);
|
||||
|
||||
Q_INVOKABLE QString getContext() const;
|
||||
Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; }
|
||||
Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; }
|
||||
Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; }
|
||||
Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE - these are intended to be public interfaces available to scripts
|
||||
Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
||||
|
@ -178,6 +199,8 @@ public:
|
|||
void scriptWarningMessage(const QString& message);
|
||||
void scriptInfoMessage(const QString& message);
|
||||
|
||||
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
|
||||
|
||||
public slots:
|
||||
void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler);
|
||||
void updateMemoryCost(const qint64&);
|
||||
|
@ -200,6 +223,30 @@ signals:
|
|||
void doneRunning();
|
||||
|
||||
protected:
|
||||
void init();
|
||||
|
||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||
void timerFired();
|
||||
void stopAllTimers();
|
||||
void stopAllTimersForEntityScript(const EntityItemID& entityID);
|
||||
void refreshFileScript(const EntityItemID& entityID);
|
||||
|
||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||
|
||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||
void stopTimer(QTimer* timer);
|
||||
|
||||
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
||||
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
|
||||
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.
|
||||
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);
|
||||
|
||||
Context _context;
|
||||
|
||||
QString _scriptContents;
|
||||
QString _parentURL;
|
||||
std::atomic<bool> _isFinished { false };
|
||||
|
@ -215,19 +262,6 @@ protected:
|
|||
bool _debuggable { false };
|
||||
qint64 _lastUpdate;
|
||||
|
||||
void init();
|
||||
|
||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||
void timerFired();
|
||||
void stopAllTimers();
|
||||
void stopAllTimersForEntityScript(const EntityItemID& entityID);
|
||||
void refreshFileScript(const EntityItemID& entityID);
|
||||
|
||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||
|
||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||
void stopTimer(QTimer* timer);
|
||||
|
||||
QString _fileNameString;
|
||||
Quat _quatLibrary;
|
||||
Vec3 _vec3Library;
|
||||
|
@ -240,15 +274,6 @@ protected:
|
|||
|
||||
AssetScriptingInterface _assetScriptingInterface{ this };
|
||||
|
||||
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
||||
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
|
||||
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.
|
||||
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);
|
||||
|
||||
std::function<bool()> _emitScriptUpdates{ [](){ return true; } };
|
||||
|
||||
std::recursive_mutex _lock;
|
||||
|
|
|
@ -62,8 +62,9 @@ void ScriptEngines::onErrorLoadingScript(const QString& url) {
|
|||
emit errorLoadingScript(url, scriptName);
|
||||
}
|
||||
|
||||
ScriptEngines::ScriptEngines()
|
||||
: _scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION)
|
||||
ScriptEngines::ScriptEngines(ScriptEngine::Context context)
|
||||
: _context(context),
|
||||
_scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION)
|
||||
{
|
||||
_scriptsModelFilter.setSourceModel(&_scriptsModel);
|
||||
_scriptsModelFilter.sort(0, Qt::AscendingOrder);
|
||||
|
@ -453,7 +454,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
|
|||
return scriptEngine;
|
||||
}
|
||||
|
||||
scriptEngine = new ScriptEngine(NO_SCRIPT, "");
|
||||
scriptEngine = new ScriptEngine(_context, NO_SCRIPT, "");
|
||||
scriptEngine->setUserLoaded(isUserLoaded);
|
||||
connect(scriptEngine, &ScriptEngine::doneRunning, this, [scriptEngine] {
|
||||
scriptEngine->deleteLater();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <SettingHandle.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
#include "ScriptsModel.h"
|
||||
#include "ScriptsModelFilter.h"
|
||||
|
||||
|
@ -34,7 +35,7 @@ class ScriptEngines : public QObject, public Dependency {
|
|||
public:
|
||||
using ScriptInitializer = std::function<void(ScriptEngine*)>;
|
||||
|
||||
ScriptEngines();
|
||||
ScriptEngines(ScriptEngine::Context context);
|
||||
void registerScriptInitializer(ScriptInitializer initializer);
|
||||
|
||||
void loadScripts();
|
||||
|
@ -90,7 +91,6 @@ public slots:
|
|||
protected slots:
|
||||
void onScriptFinished(const QString& fileNameString, ScriptEngine* engine);
|
||||
|
||||
|
||||
protected:
|
||||
friend class ScriptEngine;
|
||||
|
||||
|
@ -101,6 +101,7 @@ protected:
|
|||
void onScriptEngineError(const QString& scriptFilename);
|
||||
void launchScriptEngine(ScriptEngine* engine);
|
||||
|
||||
ScriptEngine::Context _context;
|
||||
QReadWriteLock _scriptEnginesHashLock;
|
||||
QHash<QUrl, ScriptEngine*> _scriptEnginesHash;
|
||||
QSet<ScriptEngine*> _allKnownScriptEngines;
|
||||
|
|
|
@ -156,39 +156,42 @@ void ScriptsModel::reloadDefaultFiles() {
|
|||
void ScriptsModel::requestDefaultFiles(QString marker) {
|
||||
QUrl url(defaultScriptsLocation());
|
||||
|
||||
if (url.isLocalFile()) {
|
||||
// if the url indicates a local directory, use QDirIterator
|
||||
QString localDir = expandScriptUrl(url).toLocalFile();
|
||||
int localDirPartCount = localDir.split("/").size();
|
||||
if (localDir.endsWith("/")) {
|
||||
localDirPartCount--;
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
localDirPartCount++; // one for the drive letter
|
||||
#endif
|
||||
QDirIterator it(localDir, QStringList() << "*.js", QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QUrl jsFullPath = QUrl::fromLocalFile(it.next());
|
||||
QString jsPartialPath = jsFullPath.path().split("/").mid(localDirPartCount).join("/");
|
||||
jsFullPath = normalizeScriptURL(jsFullPath);
|
||||
_treeNodes.append(new TreeNodeScript(jsPartialPath, jsFullPath.toString(), SCRIPT_ORIGIN_DEFAULT));
|
||||
}
|
||||
_loadingScripts = false;
|
||||
} else {
|
||||
// the url indicates http(s), use QNetworkRequest
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, ".");
|
||||
if (!marker.isEmpty()) {
|
||||
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
|
||||
}
|
||||
url.setQuery(query);
|
||||
// targets that don't have a scripts folder in the appropriate location will have an empty URL here
|
||||
if (!url.isEmpty()) {
|
||||
if (url.isLocalFile()) {
|
||||
// if the url indicates a local directory, use QDirIterator
|
||||
QString localDir = expandScriptUrl(url).toLocalFile();
|
||||
int localDirPartCount = localDir.split("/").size();
|
||||
if (localDir.endsWith("/")) {
|
||||
localDirPartCount--;
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
localDirPartCount++; // one for the drive letter
|
||||
#endif
|
||||
QDirIterator it(localDir, QStringList() << "*.js", QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QUrl jsFullPath = QUrl::fromLocalFile(it.next());
|
||||
QString jsPartialPath = jsFullPath.path().split("/").mid(localDirPartCount).join("/");
|
||||
jsFullPath = normalizeScriptURL(jsFullPath);
|
||||
_treeNodes.append(new TreeNodeScript(jsPartialPath, jsFullPath.toString(), SCRIPT_ORIGIN_DEFAULT));
|
||||
}
|
||||
_loadingScripts = false;
|
||||
} else {
|
||||
// the url indicates http(s), use QNetworkRequest
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, ".");
|
||||
if (!marker.isEmpty()) {
|
||||
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
|
||||
}
|
||||
url.setQuery(query);
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest request(url);
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* reply = networkAccessManager.get(request);
|
||||
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest request(url);
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* reply = networkAccessManager.get(request);
|
||||
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext&
|
|||
getInstance().printMessage((LogMsgType) type, context, message);
|
||||
}
|
||||
|
||||
const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
|
||||
void LogHandler::setupRepeatedMessageFlusher() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
// setup our timer to flush the verbose logs every 5 seconds
|
||||
|
@ -194,6 +194,11 @@ const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
|
|||
connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages);
|
||||
logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
|
||||
// make sure we setup the repeated message flusher, but do it on the LogHandler thread
|
||||
QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher");
|
||||
|
||||
QMutexLocker lock(&_mutex);
|
||||
return *_repeatedMessageRegexes.insert(regexString);
|
||||
|
|
|
@ -53,6 +53,9 @@ public:
|
|||
const QString& addRepeatedMessageRegex(const QString& regexString);
|
||||
const QString& addOnlyOnceMessageRegex(const QString& regexString);
|
||||
|
||||
private slots:
|
||||
void setupRepeatedMessageFlusher();
|
||||
|
||||
private:
|
||||
LogHandler();
|
||||
~LogHandler();
|
||||
|
|
|
@ -114,7 +114,7 @@ void Tooltip::handleAPIResponse(QNetworkReply& requestReply) {
|
|||
|
||||
if (_description.isEmpty()) {
|
||||
const QString DESCRIPTION_KEY = "description";
|
||||
// we have an empty description - did a non-empty desciption come back?
|
||||
// we have an empty description - did a non-empty description come back?
|
||||
if (placeObject.contains(DESCRIPTION_KEY)) {
|
||||
QString placeDescription = placeObject[DESCRIPTION_KEY].toString();
|
||||
|
||||
|
|
|
@ -1400,6 +1400,35 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) {
|
|||
|
||||
var ENTITY_PROPERTIES_URL = Script.resolvePath('html/entityProperties.html');
|
||||
|
||||
var ServerScriptStatusMonitor = function(entityID, statusCallback) {
|
||||
var self = this;
|
||||
|
||||
self.entityID = entityID;
|
||||
self.active = true;
|
||||
self.sendRequestTimerID = null;
|
||||
|
||||
var onStatusReceived = function(success, isRunning, status, errorInfo) {
|
||||
if (self.active) {
|
||||
statusCallback({
|
||||
statusRetrieved: success,
|
||||
isRunning: isRunning,
|
||||
status: status,
|
||||
errorInfo: errorInfo
|
||||
});
|
||||
self.sendRequestTimerID = Script.setTimeout(function() {
|
||||
if (self.active) {
|
||||
Entities.getServerScriptStatus(entityID, onStatusReceived);
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
};
|
||||
self.stop = function() {
|
||||
self.active = false;
|
||||
}
|
||||
|
||||
Entities.getServerScriptStatus(entityID, onStatusReceived);
|
||||
};
|
||||
|
||||
var PropertiesTool = function (opts) {
|
||||
var that = {};
|
||||
|
||||
|
@ -1411,6 +1440,11 @@ var PropertiesTool = function (opts) {
|
|||
|
||||
var visible = false;
|
||||
|
||||
// This keeps track of the last entity ID that was selected. If multiple entities
|
||||
// are selected or if no entity is selected this will be `null`.
|
||||
var currentSelectedEntityID = null;
|
||||
var statusMonitor = null;
|
||||
|
||||
webView.setVisible(visible);
|
||||
|
||||
that.setVisible = function (newVisible) {
|
||||
|
@ -1418,10 +1452,44 @@ var PropertiesTool = function (opts) {
|
|||
webView.setVisible(visible);
|
||||
};
|
||||
|
||||
selectionManager.addEventListener(function () {
|
||||
function updateScriptStatus(info) {
|
||||
info.type = "server_script_status";
|
||||
webView.emitScriptEvent(JSON.stringify(info));
|
||||
};
|
||||
|
||||
function resetScriptStatus() {
|
||||
updateScriptStatus({
|
||||
statusRetrieved: false,
|
||||
isRunning: false,
|
||||
status: "",
|
||||
errorInfo: ""
|
||||
});
|
||||
}
|
||||
|
||||
selectionManager.addEventListener(function (selectionUpdated) {
|
||||
var data = {
|
||||
type: 'update'
|
||||
};
|
||||
|
||||
if (selectionUpdated) {
|
||||
resetScriptStatus();
|
||||
|
||||
if (selectionManager.selections.length !== 1) {
|
||||
if (statusMonitor !== null) {
|
||||
statusMonitor.stop();
|
||||
statusMonitor = null;
|
||||
}
|
||||
currentSelectedEntityID = null;
|
||||
} else if (currentSelectedEntityID != selectionManager.selections[0]) {
|
||||
if (statusMonitor !== null) {
|
||||
statusMonitor.stop();
|
||||
}
|
||||
var entityID = selectionManager.selections[0];
|
||||
currentSelectedEntityID = entityID;
|
||||
statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus);
|
||||
}
|
||||
}
|
||||
|
||||
var selections = [];
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var entity = {};
|
||||
|
@ -1581,7 +1649,7 @@ var PropertiesTool = function (opts) {
|
|||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
}
|
||||
} else if (data.action === "reloadScript") {
|
||||
} else if (data.action === "reloadClientScripts") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
var timestamp = Date.now();
|
||||
for (i = 0; i < selectionManager.selections.length; i++) {
|
||||
|
@ -1590,6 +1658,12 @@ var PropertiesTool = function (opts) {
|
|||
});
|
||||
}
|
||||
}
|
||||
} else if (data.action === "reloadServerScripts") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
for (i = 0; i < selectionManager.selections.length; i++) {
|
||||
Entities.reloadServerScripts(selectionManager.selections[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -318,6 +318,18 @@
|
|||
<input type="text" id="property-script-url">
|
||||
<input type="button" id="reload-script-button" class="glyph" value="F">
|
||||
</div>
|
||||
<div class="behavior-group property url refresh">
|
||||
<label for="property-server-scripts">Server Script URL</label>
|
||||
<input type="text" id="property-server-scripts">
|
||||
<input type="button" id="reload-server-scripts-button" class="glyph" value="F">
|
||||
</div>
|
||||
<div class="behavior-group property">
|
||||
<label for="server-script-status">Server Script Status</label>
|
||||
<span id="server-script-status"></span>
|
||||
</div>
|
||||
<div class="behavior-group property">
|
||||
<textarea id="server-script-error"></textarea>
|
||||
</div>
|
||||
<div class="section-header model-group model-section zone-section">
|
||||
<label>Model</label><span>M</span>
|
||||
</div>
|
||||
|
|
|
@ -590,7 +590,11 @@ function loaded() {
|
|||
var elLifetime = document.getElementById("property-lifetime");
|
||||
var elScriptURL = document.getElementById("property-script-url");
|
||||
var elScriptTimestamp = document.getElementById("property-script-timestamp");
|
||||
var elReloadScriptButton = document.getElementById("reload-script-button");
|
||||
var elReloadScriptsButton = document.getElementById("reload-script-button");
|
||||
var elServerScripts = document.getElementById("property-server-scripts");
|
||||
var elReloadServerScriptsButton = document.getElementById("reload-server-scripts-button");
|
||||
var elServerScriptStatus = document.getElementById("server-script-status");
|
||||
var elServerScriptError = document.getElementById("server-script-error");
|
||||
var elUserData = document.getElementById("property-user-data");
|
||||
var elClearUserData = document.getElementById("userdata-clear");
|
||||
var elSaveUserData = document.getElementById("userdata-save");
|
||||
|
@ -708,7 +712,27 @@ function loaded() {
|
|||
var properties;
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
if (data.type == "update") {
|
||||
if (data.type == "server_script_status") {
|
||||
if (!data.statusRetrieved) {
|
||||
elServerScriptStatus.innerHTML = "Failed to retrieve status";
|
||||
elServerScriptError.style.display = "none";
|
||||
} else if (data.isRunning) {
|
||||
if (data.status == "running") {
|
||||
elServerScriptStatus.innerHTML = "Running";
|
||||
elServerScriptError.style.display = "none";
|
||||
} else if (data.status == "error_loading_script") {
|
||||
elServerScriptStatus.innerHTML = "Error loading script";
|
||||
elServerScriptError.style.display = "block";
|
||||
} else if (data.status == "error_running_script") {
|
||||
elServerScriptStatus.innerHTML = "Error running script";
|
||||
elServerScriptError.style.display = "block";
|
||||
}
|
||||
elServerScriptError.innerHTML = data.errorInfo;;
|
||||
} else {
|
||||
elServerScriptStatus.innerHTML = "Not running";
|
||||
elServerScriptError.style.display = "none";
|
||||
}
|
||||
} else if (data.type == "update") {
|
||||
|
||||
if (data.selections.length == 0) {
|
||||
if (editor !== null && lastEntityID !== null) {
|
||||
|
@ -847,6 +871,7 @@ function loaded() {
|
|||
elLifetime.value = properties.lifetime;
|
||||
elScriptURL.value = properties.script;
|
||||
elScriptTimestamp.value = properties.scriptTimestamp;
|
||||
elServerScripts.value = properties.serverScripts;
|
||||
|
||||
var json = null;
|
||||
try {
|
||||
|
@ -1143,6 +1168,7 @@ function loaded() {
|
|||
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
|
||||
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
|
||||
elScriptTimestamp.addEventListener('change', createEmitNumberPropertyUpdateFunction('scriptTimestamp'));
|
||||
elServerScripts.addEventListener('change', createEmitTextPropertyUpdateFunction('serverScripts'));
|
||||
|
||||
elClearUserData.addEventListener("click", function() {
|
||||
deleteJSONEditor();
|
||||
|
@ -1395,12 +1421,19 @@ function loaded() {
|
|||
percentage: parseFloat(elRescaleDimensionsPct.value),
|
||||
}));
|
||||
});
|
||||
elReloadScriptButton.addEventListener("click", function() {
|
||||
elReloadScriptsButton.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "reloadScript"
|
||||
action: "reloadClientScripts"
|
||||
}));
|
||||
});
|
||||
elReloadServerScriptsButton.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "reloadServerScripts"
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
window.onblur = function() {
|
||||
// Fake a change event
|
||||
|
|
|
@ -113,7 +113,7 @@ SelectionManager = (function() {
|
|||
that.selections.push(entityID);
|
||||
}
|
||||
|
||||
that._update();
|
||||
that._update(true);
|
||||
};
|
||||
|
||||
that.addEntity = function(entityID, toggleSelection) {
|
||||
|
@ -132,7 +132,7 @@ SelectionManager = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
that._update();
|
||||
that._update(true);
|
||||
};
|
||||
|
||||
that.removeEntity = function(entityID) {
|
||||
|
@ -140,15 +140,15 @@ SelectionManager = (function() {
|
|||
if (idx >= 0) {
|
||||
that.selections.splice(idx, 1);
|
||||
}
|
||||
that._update();
|
||||
that._update(true);
|
||||
};
|
||||
|
||||
that.clearSelections = function() {
|
||||
that.selections = [];
|
||||
that._update();
|
||||
that._update(true);
|
||||
};
|
||||
|
||||
that._update = function() {
|
||||
that._update = function(selectionUpdated) {
|
||||
if (that.selections.length == 0) {
|
||||
that.localDimensions = null;
|
||||
that.localPosition = null;
|
||||
|
@ -205,7 +205,7 @@ SelectionManager = (function() {
|
|||
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
try {
|
||||
listeners[i]();
|
||||
listeners[i](selectionUpdated === true);
|
||||
} catch (e) {
|
||||
print("EntitySelectionTool got exception: " + JSON.stringify(e));
|
||||
}
|
||||
|
|
|
@ -582,7 +582,7 @@ function checkNewContent() {
|
|||
if (argv.noUpdater) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Start downloading content set
|
||||
var req = request.head({
|
||||
url: HOME_CONTENT_URL
|
||||
|
@ -859,7 +859,7 @@ function onContentLoaded() {
|
|||
|
||||
if (dsPath && acPath) {
|
||||
domainServer = new Process('domain-server', dsPath, ["--get-temp-name"], logPath);
|
||||
acMonitor = new ACMonitorProcess('ac-monitor', acPath, ['-n6',
|
||||
acMonitor = new ACMonitorProcess('ac-monitor', acPath, ['-n7',
|
||||
'--log-directory', logPath,
|
||||
'--http-status-port', httpStatusPort], httpStatusPort, logPath);
|
||||
homeServer = new ProcessGroup('home', [domainServer, acMonitor]);
|
||||
|
|
Loading…
Reference in a new issue