//// EntityScriptServer.cpp// assignment-client/src/scripts//// Created by Clément Brisset on 1/5/17.// Copyright 2013 High Fidelity, Inc.// Copyright 2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #include "EntityScriptServer.h" #include <mutex> #include <AudioConstants.h>#include <AudioScriptingInterface.h>#include <AudioInjectorManager.h>#include <AvatarHashMap.h>#include <ClientServerUtils.h>#include <DebugDraw.h>#include <EntityNodeData.h>#include <EntityScriptingInterface.h>#include <LogHandler.h>#include <MessagesClient.h>#include <plugins/CodecPlugin.h>#include <plugins/PluginManager.h>#include <ResourceManager.h>#include <ResourceScriptingInterface.h>#include <ScriptCache.h>#include <ScriptEngines.h>#include <SoundCacheScriptingInterface.h>#include <UUID.h>#include <WebSocketServerClass.h> #include <EntityScriptClient.h> // for EntityScriptServerServices #include "../AssignmentDynamicFactory.h"#include "EntityScriptServerLogging.h"#include "../entities/AssignmentParentFinder.h" using Mutex = std::mutex;using Lock = std::lock_guard<Mutex>; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { auto logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message);} int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { qInstallMessageHandler(messageHandler); DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>(); DependencyManager::set<AssignmentDynamicFactory>(); DependencyManager::set<EntityScriptingInterface>(false)->setPacketSender(&_entityEditSender); DependencyManager::set<ResourceScriptingInterface>(); DependencyManager::set<ResourceManager>(); DependencyManager::set<PluginManager>()->instantiate(); DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>(); DependencyManager::set<AudioScriptingInterface>(); DependencyManager::set<ResourceCacheSharedItems>(); DependencyManager::set<SoundCache>(); DependencyManager::set<SoundCacheScriptingInterface>(); DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<ScriptCache>(); // Needed to ensure the creation of the DebugDraw instance on the main thread DebugDraw::getInstance(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleOctreePacket)); packetReceiver.registerListener(PacketType::SelectedAudioFormat, PacketReceiver::makeUnsourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleSelectedAudioFormat)); packetReceiver.registerListener(PacketType::ReloadEntityServerScript, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleReloadEntityServerScriptPacket)); packetReceiver.registerListener(PacketType::EntityScriptGetStatus, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptGetStatusPacket)); packetReceiver.registerListener(PacketType::EntityServerScriptLog, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityServerScriptLogPacket)); packetReceiver.registerListener(PacketType::EntityScriptCallMethod, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptCallMethodPacket)); static const int LOG_INTERVAL = MSECS_PER_SECOND / 10; auto timer = new QTimer(this); timer->setInterval(LOG_INTERVAL); connect(timer, &QTimer::timeout, this, &EntityScriptServer::pushLogs); timer->start();} EntityScriptServer::~EntityScriptServer() { qInstallMessageHandler(LogHandler::verboseMessageHandler);} static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server"; void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID)); if (!_entityViewer.getTree() || _shuttingDown) { return; } EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID); if (entity) { qCDebug(entity_script_server) << "Reloading: " << entityID; auto script = entity->getServerScripts(); checkAndCallPreload(entityID, script, script); } }} void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { 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); EntityTreePointer tree = _entityViewer.getTree(); if (!tree) { return; } EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); if (!entity) { return; } EntityScriptDetails details; if (_entitiesScriptManager->getEntityScriptDetails(entityID, entity->getServerScripts(), details)) { replyPacketList->writePrimitive(true); replyPacketList->writePrimitive(details.status); replyPacketList->writeString(details.errorInfo); } else { replyPacketList->writePrimitive(false); } auto nodeList = DependencyManager::get<NodeList>(); nodeList->sendPacketList(std::move(replyPacketList), *senderNode); }} void EntityScriptServer::handleSettings() { auto nodeList = DependencyManager::get<NodeList>(); auto& domainHandler = nodeList->getDomainHandler(); const QJsonObject& settingsObject = domainHandler.getSettingsObject(); commonParseSettingsObject(settingsObject); static const QString ENTITY_SCRIPT_SERVER_SETTINGS_KEY = "entity_script_server"; if (!settingsObject.contains(ENTITY_SCRIPT_SERVER_SETTINGS_KEY)) { qWarning() << "Received settings from the domain-server with no entity_script_server section."; return; } auto entityScriptServerSettings = settingsObject[ENTITY_SCRIPT_SERVER_SETTINGS_KEY].toObject(); static const QString MAX_ENTITY_PPS_OPTION = "max_total_entity_pps"; static const QString ENTITY_PPS_PER_SCRIPT = "entity_pps_per_script"; if (!entityScriptServerSettings.contains(MAX_ENTITY_PPS_OPTION) || !entityScriptServerSettings.contains(ENTITY_PPS_PER_SCRIPT)) { qWarning() << "Received settings from the domain-server with no max_total_entity_pps or entity_pps_per_script properties."; return; } _maxEntityPPS = std::max(0, entityScriptServerSettings[MAX_ENTITY_PPS_OPTION].toInt()); _entityPPSPerScript = std::max(0, entityScriptServerSettings[ENTITY_PPS_PER_SCRIPT].toInt()); qDebug() << QString("Received entity script server settings, Max Entity PPS: %1, Entity PPS Per Entity Script: %2") .arg(_maxEntityPPS).arg(_entityPPSPerScript);} void EntityScriptServer::updateEntityPPS() { int numRunningScripts = _entitiesScriptManager->getNumRunningEntityScripts(); int pps; if (std::numeric_limits<int>::max() / _entityPPSPerScript < numRunningScripts) { qWarning() << QString("Integer multiplication would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript); pps = std::numeric_limits<int>::max(); pps = std::min(_maxEntityPPS, pps); } else { pps = _entityPPSPerScript * numRunningScripts; pps = std::min(_maxEntityPPS, pps); } _entityEditSender.setPacketsPerSecond(pps);} void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { bool canRezAny = senderNode->getCanRez() || senderNode->getCanRezTmp(); bool enable = false; message->readPrimitive(&enable); auto senderUUID = senderNode->getUUID(); auto it = _logListeners.find(senderUUID); if (enable && canRezAny) { if (it == std::end(_logListeners)) { _logListeners.insert(senderUUID); qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream"; } } else { if (it != std::end(_logListeners)) { _logListeners.erase(it); qCInfo(entity_script_server) << "Node" << senderUUID << "unsubscribed from log stream"; } }} void EntityScriptServer::pushLogs() { QJsonArray buffer; { Lock lock(_logBufferMutex); std::swap(_logBuffer, buffer); } if (buffer.empty()) { return; } if (_logListeners.empty()) { return; } auto nodeList = DependencyManager::get<NodeList>(); QJsonDocument document; document.setArray(buffer); QString data(document.toJson()); std::string string = data.toStdString(); auto cstring = string.c_str(); for (auto uuid : _logListeners) { auto node = nodeList->nodeWithUUID(uuid); if (node && node->getActiveSocket()) { auto packet = NLPacketList::create(PacketType::EntityServerScriptLog, QByteArray(), true, true); packet->write(cstring, strlen(cstring)); nodeList->sendPacketList(std::move(packet), *node); } }} void EntityScriptServer::addLogEntry(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, ScriptMessage::Severity severity) { ScriptMessage entry(message, fileName, lineNumber, entityID, ScriptMessage::ScriptType::TYPE_ENTITY_SCRIPT, severity); Lock lock(_logBufferMutex); _logBuffer.append(entry.toJson());} void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode) { if (_entitiesScriptManager && _entityViewer.getTree() && !_shuttingDown) { auto entityID = QUuid::fromRfc4122(receivedMessage->read(NUM_BYTES_RFC4122_UUID)); auto method = receivedMessage->readString(); quint16 paramCount; receivedMessage->readPrimitive(¶mCount); QStringList params; for (int param = 0; param < paramCount; param++) { auto paramString = receivedMessage->readString(); params << paramString; } _entitiesScriptManager->callEntityScriptMethod(entityID, method, params, senderNode->getUUID()); }} void EntityScriptServer::run() { DependencyManager::set<ScriptEngines>(ScriptManager::ENTITY_SERVER_SCRIPT); DependencyManager::set<EntityScriptServerServices>(); DependencyManager::set<AvatarHashMap>(); // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get<NodeList>(); ThreadedAssignment::commonInit(ENTITY_SCRIPT_SERVER_LOGGING_NAME, NodeType::EntityScriptServer); // Setup MessagesClient auto messagesClient = DependencyManager::set<MessagesClient>(); messagesClient->startThread(); DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings); // 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(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); entityScriptingInterface->init(); _entityViewer.init(); // setup the JSON filter that asks for entities with a non-default serverScripts property QJsonObject queryJSONParameters; queryJSONParameters[EntityJSONQueryProperties::SERVER_SCRIPTS_PROPERTY] = EntityQueryFilterSymbol::NonDefault; QJsonObject queryFlags; queryFlags[EntityJSONQueryProperties::INCLUDE_ANCESTORS_PROPERTY] = true; queryFlags[EntityJSONQueryProperties::INCLUDE_DESCENDANTS_PROPERTY] = true; queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags; // setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter _entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters); entityScriptingInterface->setEntityTree(_entityViewer.getTree()); auto treePtr = _entityViewer.getTree(); treePtr->setIsServer(true); DependencyManager::set<AssignmentParentFinder>(treePtr); if (!_entitySimulation) { SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; simpleSimulation->setEntityTree(treePtr); treePtr->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } auto tree = treePtr.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::cleanupOldKilledListeners() { auto threshold = usecTimestampNow() - 5 * USECS_PER_SECOND; using ValueType = std::pair<QUuid, quint64>; auto it = std::remove_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) { return value.second < threshold; }); _killedListeners.erase(it, std::end(_killedListeners));} void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) { switch (activatedNode->getType()) { case NodeType::AudioMixer: negotiateAudioFormat(); break; case NodeType::Agent: { auto activatedNodeUUID = activatedNode->getUUID(); using ValueType = std::pair<QUuid, quint64>; auto it = std::find_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) { return value.first == activatedNodeUUID; }); if (it != std::end(_killedListeners)) { _killedListeners.erase(it); _logListeners.insert(activatedNodeUUID); } break; } default: // Do nothing break; }} void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) { switch (killedNode->getType()) { case NodeType::EntityServer: { // Before we clear, make sure this was our only entity server. // Otherwise we're assuming that we have "trading" entity servers // (an old one going away and a new one coming onboard) // and that we shouldn't clear here because we're still doing work. bool hasAnotherEntityServer = false; auto nodeList = DependencyManager::get<NodeList>(); nodeList->eachNodeBreakable([&hasAnotherEntityServer, &killedNode](const SharedNodePointer& node){ if (node->getType() == NodeType::EntityServer && node->getUUID() != killedNode->getUUID()) { // we're talking to > 1 entity servers, we know we won't clear hasAnotherEntityServer = true; return false; } return true; }); if (!hasAnotherEntityServer) { clear(); } break; } case NodeType::Agent: { cleanupOldKilledListeners(); auto killedNodeUUID = killedNode->getUUID(); auto it = _logListeners.find(killedNodeUUID); if (it != std::end(_logListeners)) { _logListeners.erase(killedNodeUUID); _killedListeners.emplace_back(killedNodeUUID, usecTimestampNow()); } break; } default: // Do nothing break; }} void EntityScriptServer::negotiateAudioFormat() { auto nodeList = DependencyManager::get<NodeList>(); auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat); const auto& codecPlugins = PluginManager::getInstance()->getCodecPlugins(); quint8 numberOfCodecs = (quint8)codecPlugins.size(); negotiateFormatPacket->writePrimitive(numberOfCodecs); for (const auto& plugin : codecPlugins) { auto codecName = plugin->getName(); negotiateFormatPacket->writeString(codecName); } // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); if (audioMixer) { // send off this mute packet nodeList->sendPacket(std::move(negotiateFormatPacket), *audioMixer); }} void EntityScriptServer::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) { QString selectedCodecName = message->readString(); selectAudioFormat(selectedCodecName);} void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) { _selectedCodecName = selectedCodecName; qCDebug(entity_script_server) << "Selected Codec:" << _selectedCodecName; // release any old codec encoder/decoder first... if (_codec && _encoder) { _codec->releaseEncoder(_encoder); _encoder = nullptr; _codec = nullptr; } const auto& codecPlugins = PluginManager::getInstance()->getCodecPlugins(); for (const auto& plugin : codecPlugins) { if (_selectedCodecName == plugin->getName()) { _codec = plugin; _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); qCDebug(entity_script_server) << "Selected Codec Plugin:" << _codec.get(); break; } }} void EntityScriptServer::resetEntitiesScriptEngine() { auto engineName = QString("about:Entities %1").arg(++_entitiesScriptEngineCount); auto newManager = scriptManagerFactory(ScriptManager::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName); auto newEngine = newManager->engine(); { auto guard = newEngine->getScopeGuard(); { auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor); newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); } newEngine->registerGlobalObject(guard.get(), "SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data()); newEngine->registerGlobalObject(guard.get(), "AvatarList", DependencyManager::get<AvatarHashMap>().data()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages auto scriptEngines = DependencyManager::get<ScriptEngines>().data(); connect(newManager.get(), &ScriptManager::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); connect(newManager.get(), &ScriptManager::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); connect(newManager.get(), &ScriptManager::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); connect(newManager.get(), &ScriptManager::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); // Make script engine messages available through ScriptDiscoveryService connect(newManager.get(), &ScriptManager::infoEntityMessage, scriptEngines, &ScriptEngines::infoEntityMessage); connect(newManager.get(), &ScriptManager::printedEntityMessage, scriptEngines, &ScriptEngines::printedEntityMessage); connect(newManager.get(), &ScriptManager::errorEntityMessage, scriptEngines, &ScriptEngines::errorEntityMessage); connect(newManager.get(), &ScriptManager::warningEntityMessage, scriptEngines, &ScriptEngines::warningEntityMessage);Lines 1–500 of 747. Load more
// // EntityScriptServer.cpp // assignment-client/src/scripts // // Created by Clément Brisset on 1/5/17. // Copyright 2013 High Fidelity, Inc. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "EntityScriptServer.h" #include <mutex> #include <AudioConstants.h> #include <AudioScriptingInterface.h> #include <AudioInjectorManager.h> #include <AvatarHashMap.h> #include <ClientServerUtils.h> #include <DebugDraw.h> #include <EntityNodeData.h> #include <EntityScriptingInterface.h> #include <LogHandler.h> #include <MessagesClient.h> #include <plugins/CodecPlugin.h> #include <plugins/PluginManager.h> #include <ResourceManager.h> #include <ResourceScriptingInterface.h> #include <ScriptCache.h> #include <ScriptEngines.h> #include <SoundCacheScriptingInterface.h> #include <UUID.h> #include <WebSocketServerClass.h> #include <EntityScriptClient.h> // for EntityScriptServerServices #include "../AssignmentDynamicFactory.h" #include "EntityScriptServerLogging.h" #include "../entities/AssignmentParentFinder.h" using Mutex = std::mutex; using Lock = std::lock_guard<Mutex>; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { auto logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message); } int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { qInstallMessageHandler(messageHandler); DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>(); DependencyManager::set<AssignmentDynamicFactory>(); DependencyManager::set<EntityScriptingInterface>(false)->setPacketSender(&_entityEditSender); DependencyManager::set<ResourceScriptingInterface>(); DependencyManager::set<ResourceManager>(); DependencyManager::set<PluginManager>()->instantiate(); DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>(); DependencyManager::set<AudioScriptingInterface>(); DependencyManager::set<ResourceCacheSharedItems>(); DependencyManager::set<SoundCache>(); DependencyManager::set<SoundCacheScriptingInterface>(); DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<ScriptCache>(); // Needed to ensure the creation of the DebugDraw instance on the main thread DebugDraw::getInstance(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleOctreePacket)); packetReceiver.registerListener(PacketType::SelectedAudioFormat, PacketReceiver::makeUnsourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleSelectedAudioFormat)); packetReceiver.registerListener(PacketType::ReloadEntityServerScript, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleReloadEntityServerScriptPacket)); packetReceiver.registerListener(PacketType::EntityScriptGetStatus, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptGetStatusPacket)); packetReceiver.registerListener(PacketType::EntityServerScriptLog, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityServerScriptLogPacket)); packetReceiver.registerListener(PacketType::EntityScriptCallMethod, PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptCallMethodPacket)); static const int LOG_INTERVAL = MSECS_PER_SECOND / 10; auto timer = new QTimer(this); timer->setInterval(LOG_INTERVAL); connect(timer, &QTimer::timeout, this, &EntityScriptServer::pushLogs); timer->start(); } EntityScriptServer::~EntityScriptServer() { qInstallMessageHandler(LogHandler::verboseMessageHandler); } static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server"; void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID)); if (!_entityViewer.getTree() || _shuttingDown) { return; } EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID); if (entity) { qCDebug(entity_script_server) << "Reloading: " << entityID; auto script = entity->getServerScripts(); checkAndCallPreload(entityID, script, script); } } } void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { 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); EntityTreePointer tree = _entityViewer.getTree(); if (!tree) { return; } EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); if (!entity) { return; } EntityScriptDetails details; if (_entitiesScriptManager->getEntityScriptDetails(entityID, entity->getServerScripts(), details)) { replyPacketList->writePrimitive(true); replyPacketList->writePrimitive(details.status); replyPacketList->writeString(details.errorInfo); } else { replyPacketList->writePrimitive(false); } auto nodeList = DependencyManager::get<NodeList>(); nodeList->sendPacketList(std::move(replyPacketList), *senderNode); } } void EntityScriptServer::handleSettings() { auto nodeList = DependencyManager::get<NodeList>(); auto& domainHandler = nodeList->getDomainHandler(); const QJsonObject& settingsObject = domainHandler.getSettingsObject(); commonParseSettingsObject(settingsObject); static const QString ENTITY_SCRIPT_SERVER_SETTINGS_KEY = "entity_script_server"; if (!settingsObject.contains(ENTITY_SCRIPT_SERVER_SETTINGS_KEY)) { qWarning() << "Received settings from the domain-server with no entity_script_server section."; return; } auto entityScriptServerSettings = settingsObject[ENTITY_SCRIPT_SERVER_SETTINGS_KEY].toObject(); static const QString MAX_ENTITY_PPS_OPTION = "max_total_entity_pps"; static const QString ENTITY_PPS_PER_SCRIPT = "entity_pps_per_script"; if (!entityScriptServerSettings.contains(MAX_ENTITY_PPS_OPTION) || !entityScriptServerSettings.contains(ENTITY_PPS_PER_SCRIPT)) { qWarning() << "Received settings from the domain-server with no max_total_entity_pps or entity_pps_per_script properties."; return; } _maxEntityPPS = std::max(0, entityScriptServerSettings[MAX_ENTITY_PPS_OPTION].toInt()); _entityPPSPerScript = std::max(0, entityScriptServerSettings[ENTITY_PPS_PER_SCRIPT].toInt()); qDebug() << QString("Received entity script server settings, Max Entity PPS: %1, Entity PPS Per Entity Script: %2") .arg(_maxEntityPPS).arg(_entityPPSPerScript); } void EntityScriptServer::updateEntityPPS() { int numRunningScripts = _entitiesScriptManager->getNumRunningEntityScripts(); int pps; if (std::numeric_limits<int>::max() / _entityPPSPerScript < numRunningScripts) { qWarning() << QString("Integer multiplication would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript); pps = std::numeric_limits<int>::max(); pps = std::min(_maxEntityPPS, pps); } else { pps = _entityPPSPerScript * numRunningScripts; pps = std::min(_maxEntityPPS, pps); } _entityEditSender.setPacketsPerSecond(pps); } void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { bool canRezAny = senderNode->getCanRez() || senderNode->getCanRezTmp(); bool enable = false; message->readPrimitive(&enable); auto senderUUID = senderNode->getUUID(); auto it = _logListeners.find(senderUUID); if (enable && canRezAny) { if (it == std::end(_logListeners)) { _logListeners.insert(senderUUID); qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream"; } } else { if (it != std::end(_logListeners)) { _logListeners.erase(it); qCInfo(entity_script_server) << "Node" << senderUUID << "unsubscribed from log stream"; } } } void EntityScriptServer::pushLogs() { QJsonArray buffer; { Lock lock(_logBufferMutex); std::swap(_logBuffer, buffer); } if (buffer.empty()) { return; } if (_logListeners.empty()) { return; } auto nodeList = DependencyManager::get<NodeList>(); QJsonDocument document; document.setArray(buffer); QString data(document.toJson()); std::string string = data.toStdString(); auto cstring = string.c_str(); for (auto uuid : _logListeners) { auto node = nodeList->nodeWithUUID(uuid); if (node && node->getActiveSocket()) { auto packet = NLPacketList::create(PacketType::EntityServerScriptLog, QByteArray(), true, true); packet->write(cstring, strlen(cstring)); nodeList->sendPacketList(std::move(packet), *node); } } } void EntityScriptServer::addLogEntry(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, ScriptMessage::Severity severity) { ScriptMessage entry(message, fileName, lineNumber, entityID, ScriptMessage::ScriptType::TYPE_ENTITY_SCRIPT, severity); Lock lock(_logBufferMutex); _logBuffer.append(entry.toJson()); } void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode) { if (_entitiesScriptManager && _entityViewer.getTree() && !_shuttingDown) { auto entityID = QUuid::fromRfc4122(receivedMessage->read(NUM_BYTES_RFC4122_UUID)); auto method = receivedMessage->readString(); quint16 paramCount; receivedMessage->readPrimitive(¶mCount); QStringList params; for (int param = 0; param < paramCount; param++) { auto paramString = receivedMessage->readString(); params << paramString; } _entitiesScriptManager->callEntityScriptMethod(entityID, method, params, senderNode->getUUID()); } } void EntityScriptServer::run() { DependencyManager::set<ScriptEngines>(ScriptManager::ENTITY_SERVER_SCRIPT); DependencyManager::set<EntityScriptServerServices>(); DependencyManager::set<AvatarHashMap>(); // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get<NodeList>(); ThreadedAssignment::commonInit(ENTITY_SCRIPT_SERVER_LOGGING_NAME, NodeType::EntityScriptServer); // Setup MessagesClient auto messagesClient = DependencyManager::set<MessagesClient>(); messagesClient->startThread(); DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings); // 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(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); entityScriptingInterface->init(); _entityViewer.init(); // setup the JSON filter that asks for entities with a non-default serverScripts property QJsonObject queryJSONParameters; queryJSONParameters[EntityJSONQueryProperties::SERVER_SCRIPTS_PROPERTY] = EntityQueryFilterSymbol::NonDefault; QJsonObject queryFlags; queryFlags[EntityJSONQueryProperties::INCLUDE_ANCESTORS_PROPERTY] = true; queryFlags[EntityJSONQueryProperties::INCLUDE_DESCENDANTS_PROPERTY] = true; queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags; // setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter _entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters); entityScriptingInterface->setEntityTree(_entityViewer.getTree()); auto treePtr = _entityViewer.getTree(); treePtr->setIsServer(true); DependencyManager::set<AssignmentParentFinder>(treePtr); if (!_entitySimulation) { SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; simpleSimulation->setEntityTree(treePtr); treePtr->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } auto tree = treePtr.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::cleanupOldKilledListeners() { auto threshold = usecTimestampNow() - 5 * USECS_PER_SECOND; using ValueType = std::pair<QUuid, quint64>; auto it = std::remove_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) { return value.second < threshold; }); _killedListeners.erase(it, std::end(_killedListeners)); } void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) { switch (activatedNode->getType()) { case NodeType::AudioMixer: negotiateAudioFormat(); break; case NodeType::Agent: { auto activatedNodeUUID = activatedNode->getUUID(); using ValueType = std::pair<QUuid, quint64>; auto it = std::find_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) { return value.first == activatedNodeUUID; }); if (it != std::end(_killedListeners)) { _killedListeners.erase(it); _logListeners.insert(activatedNodeUUID); } break; } default: // Do nothing break; } } void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) { switch (killedNode->getType()) { case NodeType::EntityServer: { // Before we clear, make sure this was our only entity server. // Otherwise we're assuming that we have "trading" entity servers // (an old one going away and a new one coming onboard) // and that we shouldn't clear here because we're still doing work. bool hasAnotherEntityServer = false; auto nodeList = DependencyManager::get<NodeList>(); nodeList->eachNodeBreakable([&hasAnotherEntityServer, &killedNode](const SharedNodePointer& node){ if (node->getType() == NodeType::EntityServer && node->getUUID() != killedNode->getUUID()) { // we're talking to > 1 entity servers, we know we won't clear hasAnotherEntityServer = true; return false; } return true; }); if (!hasAnotherEntityServer) { clear(); } break; } case NodeType::Agent: { cleanupOldKilledListeners(); auto killedNodeUUID = killedNode->getUUID(); auto it = _logListeners.find(killedNodeUUID); if (it != std::end(_logListeners)) { _logListeners.erase(killedNodeUUID); _killedListeners.emplace_back(killedNodeUUID, usecTimestampNow()); } break; } default: // Do nothing break; } } void EntityScriptServer::negotiateAudioFormat() { auto nodeList = DependencyManager::get<NodeList>(); auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat); const auto& codecPlugins = PluginManager::getInstance()->getCodecPlugins(); quint8 numberOfCodecs = (quint8)codecPlugins.size(); negotiateFormatPacket->writePrimitive(numberOfCodecs); for (const auto& plugin : codecPlugins) { auto codecName = plugin->getName(); negotiateFormatPacket->writeString(codecName); } // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); if (audioMixer) { // send off this mute packet nodeList->sendPacket(std::move(negotiateFormatPacket), *audioMixer); } } void EntityScriptServer::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) { QString selectedCodecName = message->readString(); selectAudioFormat(selectedCodecName); } void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) { _selectedCodecName = selectedCodecName; qCDebug(entity_script_server) << "Selected Codec:" << _selectedCodecName; // release any old codec encoder/decoder first... if (_codec && _encoder) { _codec->releaseEncoder(_encoder); _encoder = nullptr; _codec = nullptr; } const auto& codecPlugins = PluginManager::getInstance()->getCodecPlugins(); for (const auto& plugin : codecPlugins) { if (_selectedCodecName == plugin->getName()) { _codec = plugin; _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); qCDebug(entity_script_server) << "Selected Codec Plugin:" << _codec.get(); break; } } } void EntityScriptServer::resetEntitiesScriptEngine() { auto engineName = QString("about:Entities %1").arg(++_entitiesScriptEngineCount); auto newManager = scriptManagerFactory(ScriptManager::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName); auto newEngine = newManager->engine(); { auto guard = newEngine->getScopeGuard(); { auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor); newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); } newEngine->registerGlobalObject(guard.get(), "SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data()); newEngine->registerGlobalObject(guard.get(), "AvatarList", DependencyManager::get<AvatarHashMap>().data()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages auto scriptEngines = DependencyManager::get<ScriptEngines>().data(); connect(newManager.get(), &ScriptManager::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); connect(newManager.get(), &ScriptManager::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); connect(newManager.get(), &ScriptManager::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); connect(newManager.get(), &ScriptManager::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); // Make script engine messages available through ScriptDiscoveryService connect(newManager.get(), &ScriptManager::infoEntityMessage, scriptEngines, &ScriptEngines::infoEntityMessage); connect(newManager.get(), &ScriptManager::printedEntityMessage, scriptEngines, &ScriptEngines::printedEntityMessage); connect(newManager.get(), &ScriptManager::errorEntityMessage, scriptEngines, &ScriptEngines::errorEntityMessage); connect(newManager.get(), &ScriptManager::warningEntityMessage, scriptEngines, &ScriptEngines::warningEntityMessage);