diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index e79085244f..1a034e5c8a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Agent.h" + #include #include #include @@ -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()->setPacketSender(&_entityEditSender); @@ -68,7 +68,7 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); + DependencyManager::set(ScriptEngine::AGENT_SCRIPT); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); @@ -143,7 +143,7 @@ void Agent::handleAudioPacket(QSharedPointer 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(new ScriptEngine(_scriptContents, _payload)); + _scriptEngine = std::unique_ptr(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(); + _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(); diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index 75d474f566..38eb72649f 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -12,12 +12,13 @@ #include #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; } } diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 2498af8010..6674f5eb7a 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -30,6 +30,7 @@ #include "NodeType.h" #include "SendAssetTask.h" #include "UploadAssetTask.h" +#include 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); diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp index d2b3c6c256..ca8733d660 100644 --- a/assignment-client/src/assets/SendAssetTask.cpp +++ b/assignment-client/src/assets/SendAssetTask.cpp @@ -21,6 +21,7 @@ #include #include "AssetUtils.h" +#include "ClientServerUtils.h" SendAssetTask::SendAssetTask(QSharedPointer message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) : QRunnable(), diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index e09619a3cc..7e8e94c34d 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -18,6 +18,8 @@ #include #include +#include "ClientServerUtils.h" + UploadAssetTask::UploadAssetTask(QSharedPointer receivedMessage, SharedNodePointer senderNode, const QDir& resourcesDir) : diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 07639867eb..d5eb12c36d 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -398,7 +398,7 @@ void AudioMixer::start() { auto nodeList = DependencyManager::get(); // prepare the NodeList - nodeList->addNodeTypeToInterestSet(NodeType::Agent); + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); }; // parse out any AudioMixer settings diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index cd866cecb2..8c25effe45 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -681,8 +681,8 @@ void AvatarMixer::run() { void AvatarMixer::domainSettingsRequestComplete() { auto nodeList = DependencyManager::get(); - nodeList->addNodeTypeToInterestSet(NodeType::Agent); - + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); + // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); diff --git a/assignment-client/src/entities/AssignmentParentFinder.cpp b/assignment-client/src/entities/AssignmentParentFinder.cpp index a0232daff4..b8737bdc63 100644 --- a/assignment-client/src/entities/AssignmentParentFinder.cpp +++ b/assignment-client/src/entities/AssignmentParentFinder.cpp @@ -11,6 +11,8 @@ #include "AssignmentParentFinder.h" +#include + 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()) { + auto avatarHashMap = DependencyManager::get(); + parent = avatarHashMap->getAvatarBySessionID(parentID); + if (!parent.expired()) { + success = true; + return parent; + } + } + + success = false; return parent; } diff --git a/assignment-client/src/messages/MessagesMixer.cpp b/assignment-client/src/messages/MessagesMixer.cpp index 3baea67486..7622c78f35 100644 --- a/assignment-client/src/messages/MessagesMixer.cpp +++ b/assignment-client/src/messages/MessagesMixer.cpp @@ -44,8 +44,7 @@ void MessagesMixer::handleMessages(QSharedPointer 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()->addNodeTypeToInterestSet(NodeType::Agent); -} \ No newline at end of file + auto nodeList = DependencyManager::get(); + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); +} diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 0fbaf978e2..afc17d71aa 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -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); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 3e36250a82..7b681568b3 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1136,8 +1136,8 @@ void OctreeServer::domainSettingsRequestComplete() { auto nodeList = DependencyManager::get(); // 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()->getPacketReceiver(); packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp new file mode 100644 index 0000000000..7f4593910e --- /dev/null +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ClientServerUtils.h" +#include "../entities/AssignmentParentFinder.h" + +int EntityScriptServer::_entitiesScriptEngineCount = 0; + +EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { + DependencyManager::get()->setPacketSender(&_entityEditSender); + + ResourceManager::init(); + + DependencyManager::registerInheritance(); + + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + + DependencyManager::set(); + DependencyManager::set(ScriptEngine::ENTITY_SERVER_SCRIPT); + + + auto& packetReceiver = DependencyManager::get()->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(); + 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 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 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->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(); + + ThreadedAssignment::commonInit(ENTITY_SCRIPT_SERVER_LOGGING_NAME, NodeType::EntityScriptServer); + + // Setup MessagesClient + auto messagesClient = DependencyManager::set(); + 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->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(_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(); + 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 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(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().data()); + + // connect this script engines printedMessage signal to the global ScriptEngines these various messages + auto scriptEngines = DependencyManager::get().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()->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 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 for it + int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength; + + auto buffer = std::unique_ptr(new char[piggyBackedSizeWithHeader]); + memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader); + + auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr()); + message = QSharedPointer::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 message, SharedNodePointer senderNode) { + NodeType_t nodeType; + message->peekPrimitive(&nodeType); + + // PacketType_JURISDICTION, first byte is the node type... + if (nodeType == NodeType::EntityServer) { + DependencyManager::get()->getJurisdictionListener()-> + queueReceivedPacket(message, senderNode); + } +} + +void EntityScriptServer::aboutToFinish() { + shutdownScriptEngine(); + + // our entity tree is going to go away so tell that to the EntityScriptingInterface + DependencyManager::get()->setEntityTree(nullptr); + + ResourceManager::cleanup(); + + // cleanup the AudioInjectorManager (and any still running injectors) + DependencyManager::destroy(); + DependencyManager::destroy(); + + // cleanup codec & encoder + if (_codec && _encoder) { + _codec->releaseEncoder(_encoder); + _encoder = nullptr; + } +} diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h new file mode 100644 index 0000000000..9ea2d43aec --- /dev/null +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -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 + +#include +#include +#include +#include +#include + +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 message, SharedNodePointer senderNode); + void handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleSelectedAudioFormat(QSharedPointer message); + + void handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleEntityScriptGetStatusPacket(QSharedPointer 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 _entitiesScriptEngine; + EntityEditPacketSender _entityEditSender; + EntityTreeHeadlessViewer _entityViewer; + + QString _selectedCodecName; + CodecPluginPointer _codec; + Encoder* _encoder { nullptr }; +}; + +#endif // hifi_EntityScriptServer_h diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index 7cfca4f3ba..b085eefb0c 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -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 "" diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c813ffc54c..0084e51239 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -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 diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 97f3e1a697..c187239351 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -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 message) { if (message->getSize() == 0) { @@ -72,7 +71,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer(node->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(node->getLinkedData()); nodeData->setSendingSockAddr(message->getSenderSockAddr()); // guard against patched agents asking to hear about other agents @@ -128,12 +127,12 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSenderSockAddr() - << "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(node->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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(newNode->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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(newNode->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(newNode->getLinkedData()); // if we have a username from the connect request, set it on the DomainServerNodeData nodeData->setUsername(username); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0f7923519b..c741c22b83 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -107,7 +107,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : qRegisterMetaType("DomainServerWebSessionData"); qRegisterMetaTypeStreamOperators("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(static_cast(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 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(sendingNode->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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 mess sendDomainListToNode(sendingNode, message->getSenderSockAddr()); } +bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) { + auto nodeAData = static_cast(nodeA->getLinkedData()); + auto nodeBData = static_cast(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(); @@ -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(); - + 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(node->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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(nodeA->getLinkedData()); - DomainServerNodeData* nodeBData = dynamic_cast(nodeB->getLinkedData()); + DomainServerNodeData* nodeAData = static_cast(nodeA->getLinkedData()); + DomainServerNodeData* nodeBData = static_cast(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(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID); + static_cast(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(node->getLinkedData()); - return nodeData->getNodeInterestSet().contains(addedNode->getType()); + return isInInterestSet(node, addedNode); } else { return false; } @@ -1124,7 +1157,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer()->eachNode([&](const SharedNodePointer& node){ - DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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 packetList, SharedNodePointer sendingNode) { - auto nodeData = dynamic_cast(sendingNode->getLinkedData()); + auto nodeData = static_cast(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(node->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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(matchingNode->getLinkedData()); - + + auto nodeData = static_cast(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(node->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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(matchingNode->getLinkedData())->getStatsJSONObject(); + static_cast(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(QSharedPointergetType(); auto limitedNodeList = DependencyManager::get(); 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(otherNode->getLinkedData()); - return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType); + return isInInterestSet(otherNode, nodeToKill); }, [&limitedNodeList](const SharedNodePointer& otherNode){ limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode); }); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 73135695eb..4c5c42acee 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -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); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 5e1d747604..a0b80875b0 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -725,7 +725,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer(matchingNode->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(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(matchingNode->getLinkedData()); + DomainServerNodeData* nodeData = static_cast(matchingNode->getLinkedData()); machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid(); usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122()); } else { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3f19a638a0..8d1d1b90cf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -61,7 +61,7 @@ #include #include #include -#include +#include #include #include #include @@ -174,6 +174,7 @@ #include "FrameTimingsScriptingInterface.h" #include #include +#include // 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(std::bind(&Application::getUserAgent, qApp)); DependencyManager::set(); - DependencyManager::set(); + DependencyManager::set(ScriptEngine::CLIENT_SCRIPT); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -516,6 +517,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(true, qApp, qApp); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); 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(); + scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); + // connect this script engines printedMessage signal to the global ScriptEngines these various messages connect(scriptEngine, &ScriptEngine::printedMessage, DependencyManager::get().data(), &ScriptEngines::onPrintedMessage); connect(scriptEngine, &ScriptEngine::errorMessage, DependencyManager::get().data(), &ScriptEngines::onErrorMessage); diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 48b44bd3d3..86cbc98d84 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -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; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 7b80e97767..d277fd540f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -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(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); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 7890ae8275..8c021ad184 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -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); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 24136fbaad..e8353a1595 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -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) { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 917f7f3de1..a168796998 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -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; +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3e1f32ffdb..b203de203b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -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 }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 927708fc4b..d95f162061 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -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& 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 _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 EntityItemProperties::listChangedProperties() { if (scriptTimestampChanged()) { out += "scriptTimestamp"; } + if (serverScriptsChanged()) { + out += "serverScripts"; + } if (collisionSoundURLChanged()) { out += "collisionSoundURL"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index fb08182a2e..775ecc2735 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -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); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index ca7ac669f3..d52c5d9aab 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -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 diff --git a/assignment-client/src/entities/EntityNodeData.h b/libraries/entities/src/EntityNodeData.h similarity index 64% rename from assignment-client/src/entities/EntityNodeData.h rename to libraries/entities/src/EntityNodeData.h index e1f8a91030..b3a576b1ad 100644 --- a/assignment-client/src/entities/EntityNodeData.h +++ b/libraries/entities/src/EntityNodeData.h @@ -14,7 +14,7 @@ #include -#include "../octree/OctreeQueryNode.h" +#include 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 _entitiesSentLastFrame; }; #endif // hifi_EntityNodeData_h diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 127f3d0eea..b77d3cc077 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.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, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 6cdff99dac..46bc46adab 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -26,6 +26,8 @@ #include "SimulationOwner.h" #include "ZoneEntityItem.h" #include "WebEntityItem.h" +#include + EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(NULL), @@ -670,6 +672,38 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke return result; } +bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) { + auto client = DependencyManager::get(); + return client->reloadServerScript(entityID); +} + +bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) { + auto client = DependencyManager::get(); + 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); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index a0c7cdff36..0353fa08a8 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -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; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4796dda671..8856683494 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -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; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 98598b879b..cee1b10691 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -31,6 +31,9 @@ using ModelWeakPointer = std::weak_ptr; 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(); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index e374c6d289..525c1ec65f 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -16,6 +16,7 @@ #include #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((*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 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(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); diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 5b45846a74..aee8c7cfd6 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -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; diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 1f78ddd598..e09822f028 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -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; diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 315de4938e..8ace665616 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -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; diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index fd0b38d16e..8629c94eb4 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -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, diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 1f93973023..911ff224b2 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -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); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 07205e4ed6..e1cb5cd92c 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -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, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 392bd8acbe..140522b00e 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -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); diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index 4cd59488c2..7abafad627 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -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; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index b4d107b777..5f9f9124cf 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -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, diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 958b3ff88c..2a374c1d17 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -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; diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 260384ce62..4f478c8bf7 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -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, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 0dd54f7740..eaef4c5c0e 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -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; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index dd6e083efd..fbb0bdc9cf 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -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; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 0f031efd57..633aa96bfa 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -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, diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 38ececefa4..182d58ba36 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -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."; } diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 9ec3f444fb..19a7b577fe 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -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, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 39496575ad..3e21497d63 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -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); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 3e816fc094..3084d71f46 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -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, diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 10a88c0676..c0d58cd8e6 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -21,11 +21,10 @@ #include #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(); diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 13caf87b8d..8d663933ca 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -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; diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h index e5d9d119d7..1632a55336 100644 --- a/libraries/networking/src/AssetRequest.h +++ b/libraries/networking/src/AssetRequest.h @@ -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 diff --git a/libraries/networking/src/AssetUtils.h b/libraries/networking/src/AssetUtils.h index 9e508418ab..4137193274 100644 --- a/libraries/networking/src/AssetUtils.h +++ b/libraries/networking/src/AssetUtils.h @@ -19,7 +19,6 @@ #include #include -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 diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 028cdb95db..9efad15398 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -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"; } diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index 906a89774c..bbec40682f 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -33,7 +33,7 @@ public: AgentType = 2, AssetServerType = 3, MessagesMixerType = 4, - UNUSED_1 = 5, + EntityScriptServerType = 5, EntityServerType = 6, AllTypes = 7 }; diff --git a/libraries/networking/src/ClientServerUtils.h b/libraries/networking/src/ClientServerUtils.h new file mode 100644 index 0000000000..234c6d7c91 --- /dev/null +++ b/libraries/networking/src/ClientServerUtils.h @@ -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 + +using MessageID = uint32_t; +const MessageID INVALID_MESSAGE_ID = 0; + +#endif // hifi_ClientServerUtils_h diff --git a/libraries/networking/src/EntityScriptClient.cpp b/libraries/networking/src/EntityScriptClient.cpp new file mode 100644 index 0000000000..604a82fcca --- /dev/null +++ b/libraries/networking/src/EntityScriptClient.cpp @@ -0,0 +1,165 @@ +#include "EntityScriptClient.h" +#include "NodeList.h" +#include "NetworkLogging.h" +#include "EntityScriptUtils.h" + +#include + +MessageID EntityScriptClient::_currentID = 0; + +GetScriptStatusRequest::GetScriptStatusRequest(QUuid entityID) : _entityID(entityID) { +} + +GetScriptStatusRequest::~GetScriptStatusRequest() { + +} + +void GetScriptStatusRequest::start() { + auto client = DependencyManager::get(); + 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(dependency)->deleteLater(); + }); + + auto nodeList = DependencyManager::get(); + 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(); + 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(); + 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 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(); + } + } +} \ No newline at end of file diff --git a/libraries/networking/src/EntityScriptClient.h b/libraries/networking/src/EntityScriptClient.h new file mode 100644 index 0000000000..926521d9b8 --- /dev/null +++ b/libraries/networking/src/EntityScriptClient.h @@ -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 +#include + +using GetScriptStatusCallback = std::function; + +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 message, SharedNodePointer senderNode); + +private: + static MessageID _currentID; + std::unordered_map> _pendingEntityScriptStatusRequests; + + void forceFailureOfPendingRequests(SharedNodePointer node); +}; + +#endif diff --git a/libraries/networking/src/EntityScriptUtils.h b/libraries/networking/src/EntityScriptUtils.h new file mode 100644 index 0000000000..ce57525a14 --- /dev/null +++ b/libraries/networking/src/EntityScriptUtils.h @@ -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 \ No newline at end of file diff --git a/libraries/networking/src/MappingRequest.cpp b/libraries/networking/src/MappingRequest.cpp index 62a60c521b..810b5b376d 100644 --- a/libraries/networking/src/MappingRequest.cpp +++ b/libraries/networking/src/MappingRequest.cpp @@ -68,7 +68,7 @@ void GetMappingRequest::doStart() { _mappingRequestID = assetClient->getAssetMapping(_path, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer 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 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 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 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 message) { - _mappingRequestID = AssetClient::INVALID_MESSAGE_ID; + _mappingRequestID = INVALID_MESSAGE_ID; if (!responseReceived) { _error = NetworkError; } else { diff --git a/libraries/networking/src/MappingRequest.h b/libraries/networking/src/MappingRequest.h index 274d9dbd19..85b68e2427 100644 --- a/libraries/networking/src/MappingRequest.h +++ b/libraries/networking/src/MappingRequest.h @@ -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; diff --git a/libraries/networking/src/NetworkLogging.h b/libraries/networking/src/NetworkLogging.h index 30116ff405..518c600efe 100644 --- a/libraries/networking/src/NetworkLogging.h +++ b/libraries/networking/src/NetworkLogging.h @@ -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 diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 8063075e22..033f4bbaa8 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -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"); } diff --git a/libraries/networking/src/NodeType.h b/libraries/networking/src/NodeType.h index d4377f4610..5ae7a835b6 100644 --- a/libraries/networking/src/NodeType.h +++ b/libraries/networking/src/NodeType.h @@ -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(); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 46d6915a12..89f30829fd 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -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(EntityQueryPacketVersion::JsonFilter); case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5cef6013d9..23fbbff431 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -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 diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index d8c8229ce3..d2d7aba517 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -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. diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 6894f0aa1a..3ccee70871 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -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(); diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index ea1ea63353..53442f52d6 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -12,7 +12,7 @@ #ifndef hifi_OctreeConstants_h #define hifi_OctreeConstants_h -#include // for quint64 +#include // for quint64/QString #include const quint64 CHANGE_FUDGE = 1000 * 200; // useconds of fudge in determining if we want to resend changed voxels diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 61211a40d8..350ec9e5fa 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -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 diff --git a/libraries/octree/src/OctreeHeadlessViewer.h b/libraries/octree/src/OctreeHeadlessViewer.h index b43dceeba6..a502844fa5 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.h +++ b/libraries/octree/src/OctreeHeadlessViewer.h @@ -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 message, SharedNodePointer sourceNode); static void trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket); diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 60e4b5fb3a..a639eccaba 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -9,13 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include #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(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; } diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index e446e1abc7..058c1dc585 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -31,14 +31,11 @@ typedef unsigned long long quint64; #include #include +#include +#include + #include -// 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&); diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp similarity index 73% rename from assignment-client/src/octree/OctreeQueryNode.cpp rename to libraries/octree/src/OctreeQueryNode.cpp index fa844dc0f8..4ebe650f6a 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -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 #include -#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; +} diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/libraries/octree/src/OctreeQueryNode.h similarity index 93% rename from assignment-client/src/octree/OctreeQueryNode.h rename to libraries/octree/src/OctreeQueryNode.h index 96f46cb2fa..10c5598b30 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/libraries/octree/src/OctreeQueryNode.h @@ -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 #include -#include -#include -#include -#include -#include +#include "OctreeConstants.h" +#include "OctreeElementBag.h" +#include "OctreePacketData.h" +#include "OctreeQuery.h" +#include "OctreeSceneStats.h" #include "SentPacketHistory.h" #include @@ -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 _lastOctreePayload; + + QJsonObject _lastCheckJSONParameters; }; #endif // hifi_OctreeQueryNode_h diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 24b9ba95f0..af5def9775 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -27,7 +27,7 @@ class PhysicalEntitySimulation; using PhysicalEntitySimulationPointer = std::shared_ptr; using SetOfEntityMotionStates = QSet; -class PhysicalEntitySimulation :public EntitySimulation { +class PhysicalEntitySimulation : public EntitySimulation { public: PhysicalEntitySimulation(); ~PhysicalEntitySimulation(); diff --git a/libraries/plugins/src/plugins/SteamClientPlugin.h b/libraries/plugins/src/plugins/SteamClientPlugin.h index f87ebd2e66..343ed40402 100644 --- a/libraries/plugins/src/plugins/SteamClientPlugin.h +++ b/libraries/plugins/src/plugins/SteamClientPlugin.h @@ -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; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a199eb9edb..2191d45d45 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -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(); - registerGlobalObject("Recording", recordingInterface.data()); - registerGlobalObject("Tablet", DependencyManager::get().data()); - registerGlobalObject("Assets", &_assetScriptingInterface); registerGlobalObject("Resources", DependencyManager::get().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 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()) { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index d9a1249b1c..32d81b9511 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "PointerEvent.h" #include "ArrayBufferClass.h" @@ -58,16 +59,30 @@ typedef QHash 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 _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 operation); + void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); + + Context _context; + QString _scriptContents; QString _parentURL; std::atomic _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 _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 operation); - void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); - std::function _emitScriptUpdates{ [](){ return true; } }; std::recursive_mutex _lock; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 74acb2ae6a..eb3ab4abec 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -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(); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 583b6c0ea2..2fadfc81f8 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -20,6 +20,7 @@ #include #include +#include "ScriptEngine.h" #include "ScriptsModel.h" #include "ScriptsModelFilter.h" @@ -34,7 +35,7 @@ class ScriptEngines : public QObject, public Dependency { public: using ScriptInitializer = std::function; - 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 _scriptEnginesHash; QSet _allKnownScriptEngines; diff --git a/libraries/script-engine/src/ScriptsModel.cpp b/libraries/script-engine/src/ScriptsModel.cpp index 82ca26a03a..31f3a96fbd 100644 --- a/libraries/script-engine/src/ScriptsModel.cpp +++ b/libraries/script-engine/src/ScriptsModel.cpp @@ -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())); + } } } diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 35a3361c38..8e2c372bb6 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -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); diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index f80fad4c6a..ea961a8d4c 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -53,6 +53,9 @@ public: const QString& addRepeatedMessageRegex(const QString& regexString); const QString& addOnlyOnceMessageRegex(const QString& regexString); +private slots: + void setupRepeatedMessageFlusher(); + private: LogHandler(); ~LogHandler(); diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index 94e04f34b6..c0c015e72f 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -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(); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 7d5c1fed1a..d49f7ad3c5 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -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]); + } + } } } }); diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 63131bb155..1fca14c2bc 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -318,6 +318,18 @@ +
+ + + +
+
+ + +
+
+ +
M
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 3c155d83ea..6b3bdaa0a4 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -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 diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index df834b5824..2932417d25 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -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)); } diff --git a/server-console/src/main.js b/server-console/src/main.js index b387aa5dd0..cdbb1d0a3c 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -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]);