Skip to content

Commit

Permalink
Merge pull request #673 from overte-org/feature/entity_script_logging
Browse files Browse the repository at this point in the history
Entity script logging for script editor
  • Loading branch information
ksuprynowicz authored Feb 17, 2024
2 parents 63e14e0 + 5c5c529 commit 1fd3f80
Show file tree
Hide file tree
Showing 22 changed files with 747 additions and 65 deletions.
50 changes: 38 additions & 12 deletions assignment-client/src/scripts/EntityScriptServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,8 @@
using Mutex = std::mutex;
using Lock = std::lock_guard<Mutex>;

static std::mutex logBufferMutex;
static std::string logBuffer;

void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
auto logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message);

if (!logMessage.isEmpty()) {
Lock lock(logBufferMutex);
logBuffer.append(logMessage.toStdString() + '\n');
}
}

int EntityScriptServer::_entitiesScriptEngineCount = 0;
Expand Down Expand Up @@ -217,10 +209,10 @@ void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<Receiv
}

void EntityScriptServer::pushLogs() {
std::string buffer;
QJsonArray buffer;
{
Lock lock(logBufferMutex);
std::swap(logBuffer, buffer);
Lock lock(_logBufferMutex);
std::swap(_logBuffer, buffer);
}

if (buffer.empty()) {
Expand All @@ -231,16 +223,27 @@ void EntityScriptServer::pushLogs() {
}

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(buffer.data(), buffer.size());
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) {
Expand Down Expand Up @@ -469,6 +472,29 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
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);

connect(newManager.get(), &ScriptManager::infoEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_INFO);
});
connect(newManager.get(), &ScriptManager::printedEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_PRINT);
});
connect(newManager.get(), &ScriptManager::errorEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_ERROR);
});
connect(newManager.get(), &ScriptManager::warningEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_WARNING);
});

connect(newManager.get(), &ScriptManager::update, this, [this] {
_entityViewer.queryOctree();
_entityViewer.getTree()->preUpdate();
Expand Down
27 changes: 27 additions & 0 deletions assignment-client/src/scripts/EntityScriptServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include <SimpleEntitySimulation.h>
#include <ThreadedAssignment.h>
#include <ScriptManager.h>
#include <ScriptMessage.h>
#include <QJsonArray>

#include "../entities/EntityTreeHeadlessViewer.h"

Expand Down Expand Up @@ -55,10 +57,32 @@ private slots:
void handleSettings();
void updateEntityPPS();

/**
* @brief Handles log subscribe/unsubscribe requests
*
* Clients can subscribe to logs by sending a script log packet. Entity Script Server keeps list of subscribers
* and sends them logs in JSON format.
*/

void handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);

/**
* @brief Transmit logs
*
* This is called periodically through a timer to transmit logs from scripts.
*/

void pushLogs();

/**
* @brief Adds log entry to the transmit buffer
*
* This is connected to entity script log events in the script manager and adds script log message to the buffer
* containing messages that will be sent to subscribed clients.
*/

void addLogEntry(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, ScriptMessage::Severity severity);

void handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);


Expand All @@ -85,6 +109,9 @@ private slots:
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;

QJsonArray _logBuffer;
std::mutex _logBufferMutex;

int _maxEntityPPS { DEFAULT_MAX_ENTITY_PPS };
int _entityPPSPerScript { DEFAULT_ENTITY_PPS_PER_SCRIPT };

Expand Down
5 changes: 5 additions & 0 deletions interface/src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,12 @@ bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted
DependencyManager::set<CompositorHelper>();
DependencyManager::set<OffscreenQmlSurfaceCache>();
DependencyManager::set<EntityScriptClient>();

DependencyManager::set<EntityScriptServerLogClient>();
auto scriptEngines = DependencyManager::get<ScriptEngines>();
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
QObject::connect(scriptEngines.data(), &ScriptEngines::requestingEntityScriptServerLog, entityScriptServerLog.data(), &EntityScriptServerLogClient::requestMessagesForScriptEngines);

DependencyManager::set<GooglePolyScriptingInterface>();
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
DependencyManager::set<AvatarBookmarks>();
Expand Down
16 changes: 16 additions & 0 deletions libraries/entities-renderer/src/EntityTreeRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ void EntityTreeRenderer::resetPersistentEntitiesScriptEngine() {
_persistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_persistentEntitiesScriptManager);

// Make script engine messages available through ScriptDiscoveryService
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::infoEntityMessage, scriptEngines, &ScriptEngines::infoEntityMessage);
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::printedEntityMessage, scriptEngines, &ScriptEngines::printedEntityMessage);
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::errorEntityMessage, scriptEngines, &ScriptEngines::errorEntityMessage);
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::warningEntityMessage, scriptEngines, &ScriptEngines::warningEntityMessage);

_persistentEntitiesScriptManager->runInThread();
std::shared_ptr<EntitiesScriptEngineProvider> entitiesScriptEngineProvider = _persistentEntitiesScriptManager;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
Expand All @@ -255,6 +263,14 @@ void EntityTreeRenderer::resetNonPersistentEntitiesScriptEngine() {
_nonPersistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_nonPersistentEntitiesScriptManager);

// Make script engine messages available through ScriptDiscoveryService
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::infoEntityMessage, scriptEngines, &ScriptEngines::infoEntityMessage);
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::printedEntityMessage, scriptEngines, &ScriptEngines::printedEntityMessage);
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::errorEntityMessage, scriptEngines, &ScriptEngines::errorEntityMessage);
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::warningEntityMessage, scriptEngines, &ScriptEngines::warningEntityMessage);

_nonPersistentEntitiesScriptManager->runInThread();
std::shared_ptr<EntitiesScriptEngineProvider> entitiesScriptEngineProvider = _nonPersistentEntitiesScriptManager;
DependencyManager::get<EntityScriptingInterface>()->setNonPersistentEntitiesScriptEngine(entitiesScriptEngineProvider);
Expand Down
67 changes: 64 additions & 3 deletions libraries/entities/src/EntityScriptServerLogClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

#include <QJsonDocument>
#include <QJsonArray>
#include "EntityScriptServerLogClient.h"
#include "ScriptMessage.h"
#include "ScriptEngines.h"

EntityScriptServerLogClient::EntityScriptServerLogClient() {
auto nodeList = DependencyManager::get<NodeList>();
Expand All @@ -35,9 +39,9 @@ void EntityScriptServerLogClient::disconnectNotify(const QMetaMethod& signal) {

void EntityScriptServerLogClient::connectionsChanged() {
auto numReceivers = receivers(SIGNAL(receivedNewLogLines(QString)));
if (!_subscribed && numReceivers > 0) {
if (!_subscribed && (numReceivers > 0 || _areMessagesRequestedByScripts)) {
enableToEntityServerScriptLog(DependencyManager::get<NodeList>()->getThisNodeCanRez());
} else if (_subscribed && numReceivers == 0) {
} else if (_subscribed && (numReceivers == 0 && !_areMessagesRequestedByScripts)) {
enableToEntityServerScriptLog(false);
}
}
Expand All @@ -62,7 +66,59 @@ void EntityScriptServerLogClient::enableToEntityServerScriptLog(bool enable) {
}

void EntityScriptServerLogClient::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
emit receivedNewLogLines(QString::fromUtf8(message->readAll()));
QString messageText = QString::fromUtf8(message->readAll());
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(messageText.toUtf8(), &error);
emit receivedNewLogLines(messageText);
if(document.isNull()) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: Cannot parse JSON: " << error.errorString()
<< " Contents: " << messageText;
return;
}
// Iterate through contents and emit messages
if(!document.isArray()) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: JSON is not an array: " << messageText;
return;
}

auto scriptEngines = DependencyManager::get<ScriptEngines>().data();

auto array = document.array();
for (int n = 0; n < array.size(); n++) {
if (!array[n].isObject()) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: message is not an object: " << messageText;
continue;
}
ScriptMessage scriptMessage;
if (!scriptMessage.fromJson(array[n].toObject())) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: message parsing failed: " << messageText;
continue;
}
switch (scriptMessage.getSeverity()) {
case ScriptMessage::Severity::SEVERITY_INFO:
emit scriptEngines->infoEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;

case ScriptMessage::Severity::SEVERITY_PRINT:
emit scriptEngines->printedEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;

case ScriptMessage::Severity::SEVERITY_WARNING:
emit scriptEngines->warningEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;

case ScriptMessage::Severity::SEVERITY_ERROR:
emit scriptEngines->errorEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;

default:
break;
}
}
}

void EntityScriptServerLogClient::nodeActivated(SharedNodePointer activatedNode) {
Expand All @@ -84,3 +140,8 @@ void EntityScriptServerLogClient::canRezChanged(bool canRez) {
enableToEntityServerScriptLog(canRez);
}
}

void EntityScriptServerLogClient::requestMessagesForScriptEngines(bool areMessagesRequested) {
_areMessagesRequestedByScripts = areMessagesRequested;
connectionsChanged();
}
9 changes: 9 additions & 0 deletions libraries/entities/src/EntityScriptServerLogClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ class EntityScriptServerLogClient : public QObject, public Dependency {
public:
EntityScriptServerLogClient();

/**
* @brief This is called by ScriptEngines when scripts need access to entity server script messages and when access
* is not needed anymore.
*/
void requestMessagesForScriptEngines(bool areMessagesRequested);

signals:

/*@jsdoc
Expand Down Expand Up @@ -66,7 +72,10 @@ private slots:
void connectionsChanged();

private:
std::atomic<bool> _areMessagesRequestedByScripts {false};
bool _subscribed { false };

friend class ScriptEngines;
};

#endif // hifi_EntityScriptServerLogClient_h
Loading

0 comments on commit 1fd3f80

Please sign in to comment.