From 8137c59ef7645e087e1b8d7182203ad5b0879a7d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 18 Jan 2017 11:15:29 -0800 Subject: [PATCH] Add support for sending requests to entity script server --- .../src/scripts/EntityScriptServer.cpp | 34 +++- .../src/scripts/EntityScriptServer.h | 3 + interface/src/Application.cpp | 6 +- .../entities/src/EntityScriptingInterface.cpp | 1 + .../networking/src/EntityScriptClient.cpp | 153 ++++++++++++++++++ libraries/networking/src/EntityScriptClient.h | 67 ++++++++ 6 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 libraries/networking/src/EntityScriptClient.cpp create mode 100644 libraries/networking/src/EntityScriptClient.h diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 58e1d1e71b..d93fda9f1a 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -25,6 +25,8 @@ #include "../entities/AssignmentParentFinder.h" +const size_t UUID_LENGTH_BYTES = 16; + int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { @@ -52,10 +54,39 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig 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) { + auto entityID = QUuid::fromRfc4122(message->read(UUID_LENGTH_BYTES)); + + if (_entityViewer.getTree() && !_shuttingDown) { + qDebug() << "Reloading: " << entityID; + _entitiesScriptEngine->unloadEntityScript(entityID); + checkAndCallPreload(entityID, true); + } +} + +void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer message, SharedNodePointer senderNode) { + MessageID messageID; + message->readPrimitive(&messageID); + auto entityID = QUuid::fromRfc4122(message->read(UUID_LENGTH_BYTES)); + + // TODO(Huffman) Get Status + + qDebug() << "Getting script status of: " << entityID; + auto replyPacket = NLPacket::create(PacketType::EntityScriptGetStatusReply, -1, true); + replyPacket->writePrimitive(messageID); + replyPacket->writeString("running"); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(replyPacket), *senderNode); +} + void EntityScriptServer::run() { // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -75,7 +106,8 @@ void EntityScriptServer::run() { connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptServer::nodeKilled); nodeList->addSetOfNodeTypesToNodeInterestSet({ - NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer + NodeType::Agent, NodeType::AudioMixer, NodeType::AvatarMixer, + NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer }); // Setup Script Engine diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 63b1418534..d1b3363ecc 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -39,6 +39,9 @@ private slots: 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); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4f35b10a1b..5d32a830d0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -61,7 +61,7 @@ #include #include #include -#include +#include #include #include #include @@ -173,6 +173,7 @@ #include "FrameTimingsScriptingInterface.h" #include #include +#include // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -514,6 +515,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(true, qApp, qApp); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -852,7 +854,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); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index f90fddfc5a..c9ac379827 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -25,6 +25,7 @@ #include "QVariantGLM.h" #include "SimulationOwner.h" #include "ZoneEntityItem.h" +#include EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : diff --git a/libraries/networking/src/EntityScriptClient.cpp b/libraries/networking/src/EntityScriptClient.cpp new file mode 100644 index 0000000000..2d4b493f11 --- /dev/null +++ b/libraries/networking/src/EntityScriptClient.cpp @@ -0,0 +1,153 @@ +#include "EntityScriptClient.h" +#include "NodeList.h" +#include "NetworkLogging.h" + +#include + +MessageID EntityScriptClient::_currentID = 0; + +GetScriptStatusRequest::GetScriptStatusRequest(QUuid entityID) : _entityID(entityID) { +} + +GetScriptStatusRequest::~GetScriptStatusRequest() { + +} + +void GetScriptStatusRequest::start() { + auto client = DependencyManager::get(); + qDebug() << "Sending script status request"; + client->getEntityServerScriptStatus(_entityID, [this](bool responseReceived) { + qDebug() << "Received script status request"; + 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); + return INVALID_MESSAGE_ID; +} + +void EntityScriptClient::handleGetScriptStatusReply(QSharedPointer message, SharedNodePointer senderNode) { + Q_ASSERT(QThread::currentThread() == thread()); + + MessageID messageID; + message->readPrimitive(&messageID); + + auto status = 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); + 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); + } + 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..795ed3e530 --- /dev/null +++ b/libraries/networking/src/EntityScriptClient.h @@ -0,0 +1,67 @@ +// +// 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 "LimitedNodeList.h" +#include "ReceivedMessage.h" +#include "AssetUtils.h" + +#include +#include +#include + +using MessageID = uint32_t; + +using GetScriptStatusCallback = std::function; + +class GetScriptStatusRequest : public QObject { + Q_OBJECT +public: + GetScriptStatusRequest(QUuid); + ~GetScriptStatusRequest(); + + Q_INVOKABLE void start(); + +signals: + void finished(GetScriptStatusRequest* request); + +private: + QUuid _entityID; + MessageID _messageID; + QString status; +}; + +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 \ No newline at end of file