diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 8790f0eccb..bd79d0fc04 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -30,7 +30,6 @@ int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { - _entityEditSender.setPacketsPerSecond(3000); DependencyManager::get()->setPacketSender(&_entityEditSender); ResourceManager::init(); @@ -101,6 +100,52 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer(); + + auto& domainHandler = nodeList->getDomainHandler(); + const QJsonObject& settingsObject = domainHandler.getSettingsObject(); + + 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 = _entitiesScriptEngine->getNumRunningEntityScripts(); + int pps; + if (std::numeric_limits::max() / _entityPPSPerScript < numRunningScripts) { + qWarning() << QString("Integer multiplaction would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript); + pps = std::numeric_limits::max(); + pps = std::min(_maxEntityPPS, pps); + } else { + pps = _entityPPSPerScript * numRunningScripts; + pps = std::min(_maxEntityPPS, pps); + } + _entityEditSender.setPacketsPerSecond(pps); + qDebug() << QString("Updating entity PPS to: %1 @ %2 PPS per script = %3 PPS").arg(numRunningScripts).arg(_entityPPSPerScript).arg(pps); +} + void EntityScriptServer::run() { // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -115,6 +160,9 @@ void EntityScriptServer::run() { connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); messagesThread->start(); + DomainHandler& domainHandler = DependencyManager::get()->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); @@ -233,7 +281,9 @@ void EntityScriptServer::resetEntitiesScriptEngine() { newEngine->runInThread(); DependencyManager::get()->setEntitiesScriptEngine(newEngine.data()); + disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); _entitiesScriptEngine.swap(newEngine); + connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); } diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 9ea2d43aec..70f03977f8 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -20,6 +20,9 @@ #include #include +static const int DEFAULT_MAX_ENTITY_PPS = 9000; +static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; + class EntityScriptServer : public ThreadedAssignment { Q_OBJECT @@ -42,6 +45,9 @@ private slots: void handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode); void handleEntityScriptGetStatusPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleSettings(); + void updateEntityPPS(); + private: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); @@ -62,6 +68,9 @@ private: EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; + int _maxEntityPPS { DEFAULT_MAX_ENTITY_PPS }; + int _entityPPSPerScript { DEFAULT_ENTITY_PPS_PER_SCRIPT }; + QString _selectedCodecName; CodecPluginPointer _codec; Encoder* _encoder { nullptr }; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 20d2711743..dd894ecb32 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -390,7 +390,7 @@ { "name": "maximum_user_capacity_redirect_location", "label": "Redirect to Location on Maximum Capacity", - "help": "Is there another domain, you'd like to redirect clients to when the maximum number of avatars are connected.", + "help": "The location to redirect users to when the maximum number of avatars are connected.", "placeholder": "", "default": "", "advanced": false @@ -1034,6 +1034,29 @@ } ] }, + { + "name": "entity_script_server", + "label": "Entity Script Server (ESS)", + "assignment-types": [5], + "settings": [ + { + "name": "entity_pps_per_script", + "label": "Entity PPS per script", + "help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.
Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.", + "default": 900, + "type": "int", + "advanced": true + }, + { + "name": "max_total_entity_pps", + "label": "Maximum Total Entity PPS", + "help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.
Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.", + "default": 9000, + "type": "int", + "advanced": true + } + ] + }, { "name": "avatars", "label": "Avatars", diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2191d45d45..aa2817cc1f 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1393,6 +1393,16 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin } } +int ScriptEngine::getNumRunningEntityScripts() const { + int sum = 0; + for (auto& st : _entityScripts) { + if (st.status == RUNNING) { + ++sum; + } + } + return sum; +} + bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { auto it = _entityScripts.constFind(entityID); if (it == _entityScripts.constEnd()) { @@ -1456,6 +1466,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_LOADING_SCRIPT; newDetails.errorInfo = "Failed to load script"; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; } @@ -1467,6 +1478,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_RUNNING_SCRIPT; newDetails.errorInfo = "Bad syntax"; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; // done processing script } @@ -1497,6 +1509,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_RUNNING_SCRIPT; newDetails.errorInfo = exceptionMessage; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; } @@ -1523,6 +1536,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_RUNNING_SCRIPT; newDetails.errorInfo = "Could not find constructor"; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; // done processing script } @@ -1544,6 +1558,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.lastModified = lastModified; newDetails.definingSandboxURL = sandboxURL; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); if (isURL) { setParentURL(""); @@ -1575,6 +1590,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { } _entityScripts.remove(entityID); stopAllTimersForEntityScript(entityID); + emit entityScriptDetailsUpdated(); } } @@ -1596,6 +1612,7 @@ void ScriptEngine::unloadAllEntityScripts() { } } _entityScripts.clear(); + emit entityScriptDetailsUpdated(); #ifdef DEBUG_ENGINE_STATE qCDebug(scriptengine) << "---- CURRENT STATE OF ENGINE: --------------------------"; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 32d81b9511..3fc79aca9c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -199,6 +199,7 @@ public: void scriptWarningMessage(const QString& message); void scriptInfoMessage(const QString& message); + int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; public slots: @@ -222,6 +223,10 @@ signals: void reloadScript(const QString& scriptName, bool isUserLoaded); void doneRunning(); + // Emitted when an entity script is added or removed, or when the status of an entity + // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) + void entityScriptDetailsUpdated(); + protected: void init();