From ee980cbc5bae545bd80ae7bf96b186d45d5b00b6 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 24 Sep 2023 15:17:43 +0200 Subject: [PATCH 1/7] Initial script logging functions --- .../src/ConsoleScriptingInterface.cpp | 12 ++-- libraries/script-engine/src/ScriptContext.h | 7 +++ libraries/script-engine/src/ScriptManager.cpp | 17 ++++++ libraries/script-engine/src/ScriptManager.h | 60 +++++++++++++++++-- .../src/ScriptManagerScriptingInterface.cpp | 4 ++ .../src/ScriptManagerScriptingInterface.h | 47 +++++++++++++++ .../src/v8/ScriptContextV8Wrapper.h | 7 +++ .../src/v8/ScriptObjectV8Proxy.cpp | 7 +++ 8 files changed, 151 insertions(+), 10 deletions(-) diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.cpp b/libraries/script-engine/src/ConsoleScriptingInterface.cpp index 2d9d5c7ad61..ef0fffeb4db 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.cpp +++ b/libraries/script-engine/src/ConsoleScriptingInterface.cpp @@ -34,7 +34,7 @@ QList ConsoleScriptingInterface::_groupDetails = QList(); ScriptValue ConsoleScriptingInterface::info(ScriptContext* context, ScriptEngine* engine) { if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptInfoMessage(appendArguments(context)); + scriptManager->scriptInfoMessage(appendArguments(context), context->currentFileName(), context->currentLineNumber()); } return engine->nullValue(); } @@ -43,7 +43,7 @@ ScriptValue ConsoleScriptingInterface::log(ScriptContext* context, ScriptEngine* QString message = appendArguments(context); if (_groupDetails.count() == 0) { if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptPrintedMessage(message); + scriptManager->scriptPrintedMessage(message, context->currentFileName(), context->currentLineNumber()); } } else { logGroupMessage(message, engine); @@ -53,28 +53,28 @@ ScriptValue ConsoleScriptingInterface::log(ScriptContext* context, ScriptEngine* ScriptValue ConsoleScriptingInterface::debug(ScriptContext* context, ScriptEngine* engine) { if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptPrintedMessage(appendArguments(context)); + scriptManager->scriptPrintedMessage(appendArguments(context), context->currentFileName(), context->currentLineNumber()); } return engine->nullValue(); } ScriptValue ConsoleScriptingInterface::warn(ScriptContext* context, ScriptEngine* engine) { if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptWarningMessage(appendArguments(context)); + scriptManager->scriptWarningMessage(appendArguments(context), context->currentFileName(), context->currentLineNumber()); } return engine->nullValue(); } ScriptValue ConsoleScriptingInterface::error(ScriptContext* context, ScriptEngine* engine) { if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptErrorMessage(appendArguments(context)); + scriptManager->scriptErrorMessage(appendArguments(context), context->currentFileName(), context->currentLineNumber()); } return engine->nullValue(); } ScriptValue ConsoleScriptingInterface::exception(ScriptContext* context, ScriptEngine* engine) { if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptErrorMessage(appendArguments(context)); + scriptManager->scriptErrorMessage(appendArguments(context), context->currentFileName(), context->currentLineNumber()); } return engine->nullValue(); } diff --git a/libraries/script-engine/src/ScriptContext.h b/libraries/script-engine/src/ScriptContext.h index 7bc70e1080a..8eb67c42474 100644 --- a/libraries/script-engine/src/ScriptContext.h +++ b/libraries/script-engine/src/ScriptContext.h @@ -57,6 +57,13 @@ class ScriptContext { virtual int argumentCount() const = 0; virtual ScriptValue argument(int index) const = 0; virtual QStringList backtrace() const = 0; + + // Name of the file in which message was generated. Empty string when no file name is available. + virtual int currentLineNumber() const = 0; + + // Number of the line on which message was generated. -1 if there line number is not available. + virtual QString currentFileName() const = 0; + virtual ScriptValue callee() const = 0; virtual ScriptEnginePointer engine() const = 0; virtual ScriptFunctionContextPointer functionContext() const = 0; diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index f74bb01b719..06752d57a74 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -573,21 +573,38 @@ void ScriptManager::loadURL(const QUrl& scriptURL, bool reload) { void ScriptManager::scriptErrorMessage(const QString& message) { qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit errorMessage(message, getFilename()); + if (!currentEntityIdentifier.isInvalidID()) { + // TODO: add line number and proper file name + //if (engine() && engine()->currentContext() && engine()->currentContext()->) + emit errorEntityMessage(message, getFilename(), currentEntityIdentifier); + } } void ScriptManager::scriptWarningMessage(const QString& message) { qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit warningMessage(message, getFilename()); + if (!currentEntityIdentifier.isInvalidID()) { + // TODO: add line number and proper file name + emit warningEntityMessage(message, getFilename(), currentEntityIdentifier); + } } void ScriptManager::scriptInfoMessage(const QString& message) { qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit infoMessage(message, getFilename()); + if (!currentEntityIdentifier.isInvalidID()) { + // TODO: add line number and proper file name + emit infoEntityMessage(message, getFilename(), currentEntityIdentifier); + } } void ScriptManager::scriptPrintedMessage(const QString& message) { qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit printedMessage(message, getFilename()); + if (!currentEntityIdentifier.isInvalidID()) { + // TODO: add line number and proper file name + emit printedEntityMessage(message, getFilename(), currentEntityIdentifier); + } } void ScriptManager::clearDebugLogWindow() { diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h index 01d0a1dbf08..0a25f9e2e36 100644 --- a/libraries/script-engine/src/ScriptManager.h +++ b/libraries/script-engine/src/ScriptManager.h @@ -1074,8 +1074,10 @@ class ScriptManager : public QObject, public EntitiesScriptEngineProvider, publi * Emits errorMessage() * * @param message Message to send to the log + * @param fileName Name of the file in which message was generated. Empty string when no file name is available. + * @param lineNumber Number of the line on which message was generated. -1 if there line number is not available. */ - void scriptErrorMessage(const QString& message); + void scriptErrorMessage(const QString& message, const QString& fileName, int lineNumber); /** * @brief Logs a script warning message and emits an warningMessage event @@ -1083,8 +1085,10 @@ class ScriptManager : public QObject, public EntitiesScriptEngineProvider, publi * Emits warningMessage() * * @param message Message to send to the log + * @param fileName Name of the file in which message was generated. Empty string when no file name is available. + * @param lineNumber Number of the line on which message was generated. -1 if there line number is not available. */ - void scriptWarningMessage(const QString& message); + void scriptWarningMessage(const QString& message, const QString& fileName, int lineNumber); /** * @brief Logs a script info message and emits an infoMessage event @@ -1092,8 +1096,10 @@ class ScriptManager : public QObject, public EntitiesScriptEngineProvider, publi * Emits infoMessage() * * @param message Message to send to the log + * @param fileName Name of the file in which message was generated. Empty string when no file name is available. + * @param lineNumber Number of the line on which message was generated. -1 if there line number is not available. */ - void scriptInfoMessage(const QString& message); + void scriptInfoMessage(const QString& message, const QString& fileName, int lineNumber); /** * @brief Logs a script printed message and emits an printedMessage event @@ -1102,9 +1108,11 @@ class ScriptManager : public QObject, public EntitiesScriptEngineProvider, publi * Emits printedMessage() * * @param message Message to send to the log + * @param fileName Name of the file in which message was generated. Empty string when no file name is available. + * @param lineNumber Number of the line on which message was generated. -1 if there line number is not available. */ - void scriptPrintedMessage(const QString& message); + void scriptPrintedMessage(const QString& message, const QString& fileName, int lineNumber); /** * @brief Clears the debug log window @@ -1321,6 +1329,50 @@ public slots: */ void infoMessage(const QString& message, const QString& scriptName); + /** + * @brief Triggered when a client side entity script prints a message to the program log + * + * @param message + * @param fileName Name of the file in which message was generated. + * @param lineNumber Number of the line on which message was generated. + * @param entityID + */ + void printedEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + + + /** + * @brief Triggered when a client side entity script generates an error + * + * @param message + * @param fileName Name of the file in which message was generated. + * @param lineNumber Number of the line on which message was generated. + * @param entityID + */ + void errorEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + + + + /** + * @brief Triggered when a client side entity script generates a warning + * + * @param message + * @param fileName Name of the file in which message was generated. + * @param lineNumber Number of the line on which message was generated. + * @param entityID + */ + void warningEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + + + /** + * @brief Triggered when a client side entity script generates an information message + * + * @param message + * @param fileName Name of the file in which message was generated. + * @param lineNumber Number of the line on which message was generated. + * @param entityID + */ + void infoEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + /** * @brief Triggered when the running state of the script changes, e.g., from running to stopping. diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp index 7c9f264327c..5ba59a26deb 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp @@ -35,6 +35,10 @@ connect(_manager, &ScriptManager::printedMessage, this, &ScriptManagerScriptingInterface::printedMessage); connect(_manager, &ScriptManager::errorMessage, this, &ScriptManagerScriptingInterface::errorMessage); connect(_manager, &ScriptManager::warningMessage, this, &ScriptManagerScriptingInterface::warningMessage); + connect(_manager, &ScriptManager::infoEntityMessage, this, &ScriptManagerScriptingInterface::infoEntityMessage); + connect(_manager, &ScriptManager::printedEntityMessage, this, &ScriptManagerScriptingInterface::printedEntityMessage); + connect(_manager, &ScriptManager::errorEntityMessage, this, &ScriptManagerScriptingInterface::errorEntityMessage); + connect(_manager, &ScriptManager::warningEntityMessage, this, &ScriptManagerScriptingInterface::warningEntityMessage); connect(_manager, &ScriptManager::infoMessage, this, &ScriptManagerScriptingInterface::infoMessage); connect(_manager, &ScriptManager::runningStateChanged, this, &ScriptManagerScriptingInterface::runningStateChanged); connect(_manager, &ScriptManager::clearDebugWindow, this, &ScriptManagerScriptingInterface::clearDebugWindow); diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.h b/libraries/script-engine/src/ScriptManagerScriptingInterface.h index 119cbadaa6f..d25592d42e9 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.h +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.h @@ -644,6 +644,53 @@ class ScriptManagerScriptingInterface : public QObject { */ void infoMessage(const QString& message, const QString& scriptName); + /*@jsdoc + * Triggered when a client side entity script prints a message to the program log via {@link print}, {@link Script.print}, + * {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or + * {@link console.timeEnd}. + * @function Script.printedMessage + * @param {string} message - The message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @returns {Signal} + */ + void printedEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + + /*@jsdoc + * Triggered when a client side entity script generates an error, {@link console.error} or {@link console.exception} is called, or + * {@link console.assert} is called and fails. + * @function Script.errorMessage + * @param {string} message - The error message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @returns {Signal} + */ + void errorEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + + /*@jsdoc + * Triggered when a client side entity script generates a warning or {@link console.warn} is called. + * @function Script.warningMessage + * @param {string} message - The warning message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @returns {Signal} + */ + void warningEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + + /*@jsdoc + * Triggered when a client side entity script generates an information message or {@link console.info} is called. + * @function Script.infoMessage + * @param {string} message - The information message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @returns {Signal} + */ + void infoEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + /*@jsdoc * Triggered when the running state of the script changes, e.g., from running to stopping. * @function Script.runningStateChanged diff --git a/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h index c410587c458..4512e728185 100644 --- a/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h +++ b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h @@ -45,6 +45,13 @@ class ScriptContextV8Wrapper final : public ScriptContext { virtual int argumentCount() const override; virtual ScriptValue argument(int index) const override; virtual QStringList backtrace() const override; + + // Name of the file in which message was generated. Empty string when no file name is available. + virtual int currentLineNumber() const override; + + // Number of the line on which message was generated. -1 if there line number is not available. + virtual QString currentFileName() const override; + virtual ScriptValue callee() const override; virtual ScriptEnginePointer engine() const override; virtual ScriptFunctionContextPointer functionContext() const override; diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index 09e7cb59bb1..3803f09abf3 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -56,6 +56,13 @@ class ScriptPropertyContextV8Wrapper final : public ScriptContext { virtual int argumentCount() const override { return _parent->argumentCount(); } virtual ScriptValue argument(int index) const override { return _parent->argument(index); } virtual QStringList backtrace() const override { return _parent->backtrace(); } + + // Name of the file in which message was generated. Empty string when no file name is available. + virtual int currentLineNumber() const override { return _parent->currentLineNumber(); } + + // Number of the line on which message was generated. -1 if there line number is not available. + virtual QString currentFileName() const override { return _parent->currentFileName(); } + virtual ScriptValue callee() const override { return _parent->callee(); } virtual ScriptEnginePointer engine() const override { return _parent->engine(); } virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); } From f3dbfc468ea3cfc8417515c5cb2c0c9c12dbf658 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Mon, 25 Sep 2023 20:45:07 +0200 Subject: [PATCH 2/7] Added ScriptMessage class --- libraries/script-engine/src/ScriptMessage.cpp | 22 +++++++ libraries/script-engine/src/ScriptMessage.h | 57 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 libraries/script-engine/src/ScriptMessage.cpp create mode 100644 libraries/script-engine/src/ScriptMessage.h diff --git a/libraries/script-engine/src/ScriptMessage.cpp b/libraries/script-engine/src/ScriptMessage.cpp new file mode 100644 index 00000000000..b80b1e3c566 --- /dev/null +++ b/libraries/script-engine/src/ScriptMessage.cpp @@ -0,0 +1,22 @@ +// +// ScriptMessage.h +// libraries/script-engine/src/v8/FastScriptValueUtils.cpp +// +// Created by dr Karol Suprynowicz on 2023/09/24. +// 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 +// + +#include "ScriptMessage.h" + +QJsonObject ScriptMessage::toJson() { + QJsonObject object; + object["message"] = _messageContent; + object["lineNumber"] = _lineNumber; + object["fileName"] = _fileName; + object["type"] = scriptTypeToString(_scriptType); + object["severity"] = severityToString(_severity); + return object; +} \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptMessage.h b/libraries/script-engine/src/ScriptMessage.h new file mode 100644 index 00000000000..adf92d7683c --- /dev/null +++ b/libraries/script-engine/src/ScriptMessage.h @@ -0,0 +1,57 @@ +// +// ScriptMessage.h +// libraries/script-engine/src/v8/FastScriptValueUtils.cpp +// +// Created by dr Karol Suprynowicz on 2023/09/24. +// 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 +// + +#ifndef OVERTE_SCRIPTMESSAGE_H +#define OVERTE_SCRIPTMESSAGE_H + +// Used to store script messages on entity script server before transmitting them to clients who subscribed to them. +// EntityServerScriptLog packet type is used. +// In the future will also be used for storing assignment client script messages before transmission + +#include +#include + +class ScriptMessage { +public: + enum class ScriptType { + TYPE_NONE, + TYPE_ENTITY_SCRIPT + }; + enum class Severity { + SEVERITY_NONE, + SEVERITY_PRINT, + SEVERITY_INFO, + SEVERITY_DEBUG, + SEVERITY_WARNING, + SEVERITY_ERROR + }; + + static QString scriptTypeToString(ScriptType scriptType); + static QString severityToString(Severity severity); + + static ScriptType scriptTypeFromString(ScriptType scriptType); + static Severity severityFromString(Severity severity); + + ScriptMessage() {}; + ScriptMessage(QString messageContent, QString fileName, int lineNumber, ScriptType scriptType, Severity severity) + : _messageContent(messageContent), _fileName(fileName), _lineNumber(lineNumber), _scriptType(scriptType) {} + + QJsonObject toJson(); + +private: + QString _messageContent; + QString _fileName; + int _lineNumber {-1}; + ScriptType _scriptType {ScriptType::TYPE_NONE}; + Severity _severity {Severity::SEVERITY_NONE}; +}; + +#endif //OVERTE_SCRIPTMESSAGE_H From 274867bc47cf742286a11e3e48461f1c3a1dae75 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Wed, 18 Oct 2023 19:19:32 +0200 Subject: [PATCH 3/7] Work on better subscribtion to script messages --- .../src/ConsoleScriptingInterface.cpp | 14 +- libraries/script-engine/src/ScriptManager.cpp | 130 +++++++++++++----- libraries/script-engine/src/ScriptMessage.cpp | 22 ++- libraries/script-engine/src/ScriptMessage.h | 7 +- .../src/v8/ScriptContextV8Wrapper.cpp | 31 +++++ .../script-engine/src/v8/ScriptEngineV8.cpp | 35 ++++- .../script-engine/src/v8/ScriptEngineV8.h | 2 + .../src/v8/ScriptObjectV8Proxy.cpp | 8 +- .../src/v8/ScriptValueV8Wrapper.cpp | 13 +- 9 files changed, 209 insertions(+), 53 deletions(-) diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.cpp b/libraries/script-engine/src/ConsoleScriptingInterface.cpp index ef0fffeb4db..1c4c07dddd3 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.cpp +++ b/libraries/script-engine/src/ConsoleScriptingInterface.cpp @@ -84,7 +84,7 @@ void ConsoleScriptingInterface::time(QString labelName) { QString message = QString("%1: Timer started").arg(labelName); Q_ASSERT(engine); if (ScriptManager* scriptManager = engine()->manager()) { - scriptManager->scriptPrintedMessage(message); + scriptManager->scriptPrintedMessage(message, context()->currentFileName(), context()->currentLineNumber()); } } @@ -92,13 +92,13 @@ void ConsoleScriptingInterface::timeEnd(QString labelName) { Q_ASSERT(engine); if (ScriptManager* scriptManager = engine()->manager()) { if (!_timerDetails.contains(labelName)) { - scriptManager->scriptErrorMessage("No such label found " + labelName); + scriptManager->scriptErrorMessage("No such label found " + labelName, context()->currentFileName(), context()->currentLineNumber()); return; } if (_timerDetails.value(labelName).isNull()) { _timerDetails.remove(labelName); - scriptManager->scriptErrorMessage("Invalid start time for " + labelName); + scriptManager->scriptErrorMessage("Invalid start time for " + labelName, context()->currentFileName(), context()->currentLineNumber()); return; } QDateTime _startTime = _timerDetails.value(labelName); @@ -108,7 +108,7 @@ void ConsoleScriptingInterface::timeEnd(QString labelName) { QString message = QString("%1: %2ms").arg(labelName).arg(QString::number(diffInMS)); _timerDetails.remove(labelName); - scriptManager->scriptPrintedMessage(message); + scriptManager->scriptPrintedMessage(message, context()->currentFileName(), context()->currentLineNumber()); } } @@ -131,7 +131,7 @@ ScriptValue ConsoleScriptingInterface::assertion(ScriptContext* context, ScriptE assertionResult = QString("Assertion failed : %1").arg(message); } if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptErrorMessage(assertionResult); + scriptManager->scriptErrorMessage(assertionResult, context->currentFileName(), context->currentLineNumber()); } } return engine->nullValue(); @@ -143,7 +143,7 @@ void ConsoleScriptingInterface::trace() { if (ScriptManager* scriptManager = scriptEngine->manager()) { scriptManager->scriptPrintedMessage (QString(STACK_TRACE_FORMAT).arg(LINE_SEPARATOR, - scriptEngine->currentContext()->backtrace().join(LINE_SEPARATOR))); + scriptEngine->currentContext()->backtrace().join(LINE_SEPARATOR)), context()->currentFileName(), context()->currentLineNumber()); } } @@ -190,6 +190,6 @@ void ConsoleScriptingInterface::logGroupMessage(QString message, ScriptEngine* e } logMessage.append(message); if (ScriptManager* scriptManager = engine->manager()) { - scriptManager->scriptPrintedMessage(logMessage); + scriptManager->scriptPrintedMessage(logMessage, context()->currentFileName(), context()->currentLineNumber()); } } diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index 06752d57a74..ccdc917f0b4 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -225,7 +225,12 @@ QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID) QString ScriptManager::logException(const ScriptValue& exception) { auto message = formatException(exception, _enableExtendedJSExceptions.get()); - scriptErrorMessage(message); + auto context = _engine->currentContext(); + if (context) { + scriptErrorMessage(message, context->currentFileName(), context->currentLineNumber()); + } else { + scriptErrorMessage(message, "", -1); + } return message; } @@ -330,6 +335,11 @@ ScriptManager::ScriptManager(Context context, const QString& scriptContents, con }); } + //Gather entity script messages for transmission when running server side. + if (_type == Type::ENTITY_SERVER) { + ; + } + if (!_areMetaTypesInitialized) { initMetaTypes(); } @@ -514,7 +524,7 @@ void ScriptManager::waitTillDoneRunning(bool shutdown) { } #endif - scriptInfoMessage("Script Engine has stopped:" + getFilename()); + scriptInfoMessage("Script Engine has stopped:" + getFilename(), "", -1); } } @@ -549,7 +559,7 @@ void ScriptManager::loadURL(const QUrl& scriptURL, bool reload) { // Check that script has a supported file extension if (!hasValidScriptSuffix(_fileNameString)) { - scriptErrorMessage("File extension of file: " + _fileNameString + " is not a currently supported script type"); + scriptErrorMessage("File extension of file: " + _fileNameString + " is not a currently supported script type", _fileNameString, -1); emit errorLoadingScript(_fileNameString); return; } @@ -559,7 +569,7 @@ void ScriptManager::loadURL(const QUrl& scriptURL, bool reload) { scriptCache->getScriptContents(url.toString(), [this](const QString& url, const QString& scriptContents, bool isURL, bool success, const QString&status) { qCDebug(scriptengine) << "loadURL" << url << status << QThread::currentThread(); if (!success) { - scriptErrorMessage("ERROR Loading file (" + status + "):" + url); + scriptErrorMessage("ERROR Loading file (" + status + "):" + url, url, -1); emit errorLoadingScript(_fileNameString); return; } @@ -570,40 +580,35 @@ void ScriptManager::loadURL(const QUrl& scriptURL, bool reload) { }, reload, maxRetries); } -void ScriptManager::scriptErrorMessage(const QString& message) { +void ScriptManager::scriptErrorMessage(const QString& message, const QString& fileName, int lineNumber) { qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit errorMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - // TODO: add line number and proper file name - //if (engine() && engine()->currentContext() && engine()->currentContext()->) - emit errorEntityMessage(message, getFilename(), currentEntityIdentifier); + emit errorEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); } } -void ScriptManager::scriptWarningMessage(const QString& message) { +void ScriptManager::scriptWarningMessage(const QString& message, const QString& fileName, int lineNumber) { qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit warningMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - // TODO: add line number and proper file name - emit warningEntityMessage(message, getFilename(), currentEntityIdentifier); + emit warningEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); } } -void ScriptManager::scriptInfoMessage(const QString& message) { +void ScriptManager::scriptInfoMessage(const QString& message, const QString& fileName, int lineNumber) { qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit infoMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - // TODO: add line number and proper file name - emit infoEntityMessage(message, getFilename(), currentEntityIdentifier); + emit infoEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); } } -void ScriptManager::scriptPrintedMessage(const QString& message) { +void ScriptManager::scriptPrintedMessage(const QString& message, const QString& fileName, int lineNumber) { qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit printedMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - // TODO: add line number and proper file name - emit printedEntityMessage(message, getFilename(), currentEntityIdentifier); + emit printedEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); } } @@ -929,7 +934,7 @@ void ScriptManager::run() { return; // bail early - avoid setting state in init(), as evaluate() will bail too } - scriptInfoMessage("Script Engine starting:" + getFilename()); + scriptInfoMessage("Script Engine starting:" + getFilename(), getFilename(), -1); if (!_isInitialized) { init(); @@ -1081,7 +1086,7 @@ void ScriptManager::run() { _engine->clearExceptions(); } } - scriptInfoMessage("Script Engine stopping:" + getFilename()); + scriptInfoMessage("Script Engine stopping:" + getFilename(), getFilename(), -1); stopAllTimers(); // make sure all our timers are stopped if the script is ending emit scriptEnding(); @@ -1156,7 +1161,7 @@ void ScriptManager::updateMemoryCost(const qint64& deltaSize) { void ScriptManager::timerFired() { if (isStopped()) { - scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename()); + scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename(), getFilename(), -1); return; // bail early } @@ -1223,7 +1228,14 @@ QTimer* ScriptManager::setupTimerWithInterval(const ScriptValue& function, int i QTimer* ScriptManager::setInterval(const ScriptValue& function, int intervalMS) { if (isStopped()) { - scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename()); + int lineNumber = -1; + QString fileName = getFilename(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } + scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename(), fileName, lineNumber); return NULL; // bail early } @@ -1232,7 +1244,14 @@ QTimer* ScriptManager::setInterval(const ScriptValue& function, int intervalMS) QTimer* ScriptManager::setTimeout(const ScriptValue& function, int timeoutMS) { if (isStopped()) { - scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename()); + int lineNumber = -1; + QString fileName = getFilename(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } + scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename(), fileName, lineNumber); return NULL; // bail early } @@ -1668,8 +1687,15 @@ void ScriptManager::include(const QStringList& includeFiles, const ScriptValue& return; } if (isStopped()) { + int lineNumber = -1; + QString fileName = getFilename(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:" - + includeFiles.join(",") + "parent script:" + getFilename()); + + includeFiles.join(",") + "parent script:" + getFilename(), fileName, lineNumber); return; // bail early } QList urls; @@ -1682,8 +1708,15 @@ void ScriptManager::include(const QStringList& includeFiles, const ScriptValue& thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file))); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); if (!defaultScriptsLoc.isParentOf(thisURL)) { + int lineNumber = -1; + QString fileName = getFilename(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } //V8TODO this probably needs to be done per context, otherwise file cannot be included again in a module - scriptWarningMessage("Script.include() -- skipping" + file + "-- outside of standard libraries"); + scriptWarningMessage("Script.include() -- skipping" + file + "-- outside of standard libraries", fileName, lineNumber); continue; } isStandardLibrary = true; @@ -1693,8 +1726,15 @@ void ScriptManager::include(const QStringList& includeFiles, const ScriptValue& bool disallowOutsideFiles = thisURL.isLocalFile() && !isStandardLibrary && !currentSandboxURL.isLocalFile(); if (disallowOutsideFiles && !PathUtils::isDescendantOf(thisURL, currentSandboxURL)) { + int lineNumber = -1; + QString fileName = currentSandboxURL.toString(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString() - + "outside of original entity script" + currentSandboxURL.toString()); + + "outside of original entity script" + currentSandboxURL.toString(), fileName, lineNumber); } else { // We could also check here for CORS, but we don't yet. // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. @@ -1716,7 +1756,14 @@ void ScriptManager::include(const QStringList& includeFiles, const ScriptValue& for (QUrl url : urls) { QString contents = data[url]; if (contents.isNull()) { - scriptErrorMessage("Error loading file (" + status[url] +"): " + url.toString()); + int lineNumber = -1; + QString fileName = url.toString(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } + scriptErrorMessage("Error loading file (" + status[url] +"): " + url.toString(), fileName, lineNumber); } else { std::lock_guard lock(_lock); if (!_includedURLs.contains(url)) { @@ -1736,7 +1783,14 @@ void ScriptManager::include(const QStringList& includeFiles, const ScriptValue& _engine->clearExceptions(); } } else { - scriptPrintedMessage("Script.include() skipping evaluation of previously included url:" + url.toString()); + int lineNumber = -1; + QString fileName = url.toString(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } + scriptPrintedMessage("Script.include() skipping evaluation of previously included url:" + url.toString(), fileName, lineNumber); } } } @@ -1765,8 +1819,15 @@ void ScriptManager::include(const QStringList& includeFiles, const ScriptValue& void ScriptManager::include(const QString& includeFile, const ScriptValue& callback) { if (isStopped()) { + int lineNumber = -1; + QString fileName = currentSandboxURL.toString(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:" - + includeFile + "parent script:" + getFilename()); + + includeFile + "parent script:" + getFilename(), fileName, lineNumber); return; // bail early } @@ -1782,14 +1843,21 @@ void ScriptManager::load(const QString& loadFile) { if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { return; } + int lineNumber = -1; + QString fileName = getFilename(); + auto context = _engine->currentContext(); + if (context) { + lineNumber = context->currentLineNumber(); + fileName = context->currentFileName(); + } if (isStopped()) { scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:" - + loadFile + "parent script:" + getFilename()); + + loadFile + "parent script:" + getFilename(), fileName, lineNumber); return; // bail early } if (!currentEntityIdentifier.isInvalidID()) { scriptWarningMessage("Script.load() from entity script is ignored... loadFile:" - + loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString()); + + loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString(), fileName, lineNumber); return; // bail early } @@ -2457,7 +2525,7 @@ void ScriptManager::refreshFileScript(const EntityItemID& entityID) { QString filePath = QUrl(details.scriptText).toLocalFile(); auto lastModified = QFileInfo(filePath).lastModified().toMSecsSinceEpoch(); if (lastModified > details.lastModified) { - scriptInfoMessage("Reloading modified script " + details.scriptText); + scriptInfoMessage("Reloading modified script " + details.scriptText, filePath, -1); loadEntityScript(entityID, details.scriptText, true); } } diff --git a/libraries/script-engine/src/ScriptMessage.cpp b/libraries/script-engine/src/ScriptMessage.cpp index b80b1e3c566..cb920fa5691 100644 --- a/libraries/script-engine/src/ScriptMessage.cpp +++ b/libraries/script-engine/src/ScriptMessage.cpp @@ -11,12 +11,30 @@ #include "ScriptMessage.h" +#include + QJsonObject ScriptMessage::toJson() { QJsonObject object; object["message"] = _messageContent; object["lineNumber"] = _lineNumber; object["fileName"] = _fileName; - object["type"] = scriptTypeToString(_scriptType); - object["severity"] = severityToString(_severity); + object["type"] = static_cast(_scriptType); + object["severity"] = static_cast(_severity); return object; +} + +bool ScriptMessage::fromJson(const QJsonObject &object) { + if (!object["message"].isString() + || !object["lineNumber"].isDouble() + || !object["fileName"].isString() + || !object["type"].isDouble() + || !object["severity"].isDouble()) { + qDebug() << "ScriptMessage::fromJson failed to find required fields in JSON file"; + return false; + } + _messageContent = object["message"].toString(); + _lineNumber = object["lineNumber"].toInt(); + _fileName = object["fileName"].toInt(); + _scriptType = static_cast(object["type"].toInt()); + _severity = static_cast(object["severity"].toInt()); } \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptMessage.h b/libraries/script-engine/src/ScriptMessage.h index adf92d7683c..a0a9f52615c 100644 --- a/libraries/script-engine/src/ScriptMessage.h +++ b/libraries/script-engine/src/ScriptMessage.h @@ -34,17 +34,12 @@ class ScriptMessage { SEVERITY_ERROR }; - static QString scriptTypeToString(ScriptType scriptType); - static QString severityToString(Severity severity); - - static ScriptType scriptTypeFromString(ScriptType scriptType); - static Severity severityFromString(Severity severity); - ScriptMessage() {}; ScriptMessage(QString messageContent, QString fileName, int lineNumber, ScriptType scriptType, Severity severity) : _messageContent(messageContent), _fileName(fileName), _lineNumber(lineNumber), _scriptType(scriptType) {} QJsonObject toJson(); + bool fromJson(const QJsonObject &object); private: QString _messageContent; diff --git a/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp index 2b92a9ae8a5..11e90b3a5ab 100644 --- a/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp +++ b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp @@ -111,6 +111,37 @@ QStringList ScriptContextV8Wrapper::backtrace() const { return backTrace; } +int ScriptContextV8Wrapper::currentLineNumber() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local stackTrace = v8::StackTrace::CurrentStackTrace(isolate, 1); + if (stackTrace->GetFrameCount() > 0) { + v8::Local stackFrame = stackTrace->GetFrame(isolate, 0); + return stackFrame->GetLineNumber(); + } else { + return -1; + } +} + +QString ScriptContextV8Wrapper::currentFileName() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local stackTrace = v8::StackTrace::CurrentStackTrace(isolate, 1); + QStringList backTrace; + if (stackTrace->GetFrameCount() > 0) { + v8::Local stackFrame = stackTrace->GetFrame(isolate, 0); + return *v8::String::Utf8Value(isolate, stackFrame->GetScriptNameOrSourceURL()); + } else { + return ""; + } +} + ScriptValue ScriptContextV8Wrapper::callee() const { Q_ASSERT(false); //V8TODO diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp index 61dbdd00e08..d10b1c2d159 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -77,6 +77,17 @@ bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QStri return false; } +QString getFileNameFromTryCatch(v8::TryCatch &tryCatch, v8::Isolate *isolate, v8::Local &context ) { + v8::Local exceptionMessage = tryCatch.Message(); + QString errorFileName; + auto resource = exceptionMessage->GetScriptResourceName(); + v8::Local v8resourceString; + if (resource->ToString(context).ToLocal(&v8resourceString)) { + errorFileName = QString(*v8::String::Utf8Value(isolate, v8resourceString)); + } + return errorFileName; +} + ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); @@ -726,7 +737,13 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, + "tryCatch details:" + formatErrorMessageFromTryCatch(tryCatch); v8Result = v8::Null(_v8Isolate); if (_manager) { - _manager->scriptErrorMessage(errorMessage); + v8::Local exceptionMessage = tryCatch.Message(); + int errorLineNumber = -1; + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(closureContext).FromJust(); + } + _manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatch, _v8Isolate, closureContext), + errorLineNumber); } else { qWarning(scriptengine_v8) << errorMessage; } @@ -781,7 +798,13 @@ ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& f if (!v8::Script::Compile(context, v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { QString errorMessage(QString("Error while compiling script: \"") + fileName + QString("\" ") + formatErrorMessageFromTryCatch(tryCatch)); if (_manager) { - _manager->scriptErrorMessage(errorMessage); + v8::Local exceptionMessage = tryCatch.Message(); + int errorLineNumber = -1; + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust(); + } + _manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatch, _v8Isolate, context), + errorLineNumber); } else { qDebug(scriptengine_v8) << errorMessage; } @@ -799,7 +822,13 @@ ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& f ScriptValue errorValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get()))); QString errorMessage(QString("Running script: \"") + fileName + QString("\" ") + formatErrorMessageFromTryCatch(tryCatchRun)); if (_manager) { - _manager->scriptErrorMessage(errorMessage); + v8::Local exceptionMessage = tryCatchRun.Message(); + int errorLineNumber = -1; + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust(); + } + _manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatchRun, _v8Isolate, context), + errorLineNumber); } else { qDebug(scriptengine_v8) << errorMessage; } diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.h b/libraries/script-engine/src/v8/ScriptEngineV8.h index 5badba271ef..02352c9ed78 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.h +++ b/libraries/script-engine/src/v8/ScriptEngineV8.h @@ -309,6 +309,8 @@ class ContextScopeV8 { ScriptEngineV8* _engine; }; +QString getFileNameFromTryCatch(v8::TryCatch &tryCatch, v8::Isolate *isolate, v8::Local &context ); + #include "V8Types.h" #endif // hifi_ScriptEngineV8_h diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index 3803f09abf3..48572c30f97 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -1281,8 +1281,14 @@ int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** argu QString errorMessage(QString("Signal proxy ") + fullName() + " connection call failed: \"" + _engine->formatErrorMessageFromTryCatch(tryCatch) + "\nThis provided: " + QString::number(conn.thisValue.get()->IsObject())); + v8::Local exceptionMessage = tryCatch.Message(); + int errorLineNumber = -1; + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust(); + } if (_engine->_manager) { - _engine->_manager->scriptErrorMessage(errorMessage); + _engine->_manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatch, isolate, context), + errorLineNumber); } else { qDebug(scriptengine_v8) << errorMessage; } diff --git a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp index 40bfde08076..7f9faf21f20 100644 --- a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp +++ b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp @@ -104,7 +104,13 @@ ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const Scri if (tryCatch.HasCaught()) { QString errorMessage(QString("Function call failed: \"") + _engine->formatErrorMessageFromTryCatch(tryCatch)); if (_engine->_manager) { - _engine->_manager->scriptErrorMessage(errorMessage); + v8::Local exceptionMessage = tryCatch.Message(); + int errorLineNumber = -1; + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust(); + } + _engine->_manager->scriptErrorMessage(errorMessage, getFileNameFromTryCatch(tryCatch, isolate, context), + errorLineNumber); } else { qDebug(scriptengine_v8) << errorMessage; } @@ -114,9 +120,10 @@ ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const Scri if (maybeResult.ToLocal(&result)) { return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result))); } else { - QString errorMessage("JS function call failed: " + _engine->currentContext()->backtrace().join("\n")); + auto currentContext = _engine->currentContext(); + QString errorMessage("JS function call failed: " + currentContext->backtrace().join("\n")); if (_engine->_manager) { - _engine->_manager->scriptErrorMessage(errorMessage); + _engine->_manager->scriptErrorMessage(errorMessage, currentContext->currentFileName(), currentContext->currentLineNumber()); } else { qDebug(scriptengine_v8) << errorMessage; } From dfbc1d8865c6f260f20e7881eb2846cfc846fb3a Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Fri, 29 Dec 2023 20:28:54 +0100 Subject: [PATCH 4/7] Server side entity script messages --- .../src/scripts/EntityScriptServer.cpp | 48 +++++++++---- .../src/scripts/EntityScriptServer.h | 27 ++++++++ .../src/EntityTreeRenderer.cpp | 16 +++++ .../src/EntityScriptServerLogClient.cpp | 58 +++++++++++++++- libraries/script-engine/src/ScriptEngines.h | 55 +++++++++++++++ libraries/script-engine/src/ScriptManager.cpp | 8 +-- libraries/script-engine/src/ScriptManager.h | 12 ++-- .../src/ScriptManagerScriptingInterface.cpp | 20 ++++-- .../src/ScriptManagerScriptingInterface.h | 67 ++++++------------- libraries/script-engine/src/ScriptMessage.cpp | 3 + libraries/script-engine/src/ScriptMessage.h | 13 +++- 11 files changed, 252 insertions(+), 75 deletions(-) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index b16e4561d64..59302d3696c 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -44,16 +44,8 @@ using Mutex = std::mutex; using Lock = std::lock_guard; -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; @@ -217,10 +209,10 @@ void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer(); + QJsonDocument document; + document.setArray(buffer); + QString data(document.toJson()); 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(data.toStdString().c_str(), data.size()); 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, SharedNodePointer senderNode) { if (_entitiesScriptManager && _entityViewer.getTree() && !_shuttingDown) { @@ -469,6 +470,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(); diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 3f15f5733c5..2fba985ef42 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include "../entities/EntityTreeHeadlessViewer.h" @@ -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 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 message, SharedNodePointer senderNode); @@ -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 }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 4861bc6ecba..cdc31a3765f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -232,6 +232,14 @@ void EntityTreeRenderer::resetPersistentEntitiesScriptEngine() { _persistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, QString("about:Entities %1").arg(++_entitiesScriptEngineCount)); DependencyManager::get()->runScriptInitializers(_persistentEntitiesScriptManager); + + // Make script engine messages available through ScriptDiscoveryService + auto scriptEngines = DependencyManager::get().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 = _persistentEntitiesScriptManager; auto entityScriptingInterface = DependencyManager::get(); @@ -255,6 +263,14 @@ void EntityTreeRenderer::resetNonPersistentEntitiesScriptEngine() { _nonPersistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, QString("about:Entities %1").arg(++_entitiesScriptEngineCount)); DependencyManager::get()->runScriptInitializers(_nonPersistentEntitiesScriptManager); + + // Make script engine messages available through ScriptDiscoveryService + auto scriptEngines = DependencyManager::get().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 = _nonPersistentEntitiesScriptManager; DependencyManager::get()->setNonPersistentEntitiesScriptEngine(entitiesScriptEngineProvider); diff --git a/libraries/entities/src/EntityScriptServerLogClient.cpp b/libraries/entities/src/EntityScriptServerLogClient.cpp index 5d7d4017cda..5c21f017761 100644 --- a/libraries/entities/src/EntityScriptServerLogClient.cpp +++ b/libraries/entities/src/EntityScriptServerLogClient.cpp @@ -9,7 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include #include "EntityScriptServerLogClient.h" +#include "ScriptMessage.h" +#include "ScriptEngines.h" EntityScriptServerLogClient::EntityScriptServerLogClient() { auto nodeList = DependencyManager::get(); @@ -62,7 +66,59 @@ void EntityScriptServerLogClient::enableToEntityServerScriptLog(bool enable) { } void EntityScriptServerLogClient::handleEntityServerScriptLogPacket(QSharedPointer 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().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) { diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 671789bd8e3..d8d50a4c201 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -256,6 +256,57 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize */ void infoMessage(const QString& message, const QString& engineName); + /*@jsdoc + * Triggered when a client side entity script prints a message to the program log via {@link print}, {@link Script.print}, + * {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or + * {@link console.timeEnd}. + * @function Script.printedMessage + * @param {string} message - The message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side. + * @returns {Signal} + */ + void printedEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); + + /*@jsdoc + * Triggered when a client side entity script generates an error, {@link console.error} or {@link console.exception} is called, or + * {@link console.assert} is called and fails. + * @function Script.errorMessage + * @param {string} message - The error message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side. + * @returns {Signal} + */ + void errorEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); + + /*@jsdoc + * Triggered when a client side entity script generates a warning or {@link console.warn} is called. + * @function Script.warningMessage + * @param {string} message - The warning message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side. + * @returns {Signal} + */ + void warningEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); + + /*@jsdoc + * Triggered when a client side entity script generates an information message or {@link console.info} is called. + * @function Script.infoMessage + * @param {string} message - The information message. + * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. + * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. + * @param {Uuid} entityID - Entity ID. + * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side. + * @returns {Signal} + */ + void infoEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); + /*@jsdoc * @function ScriptDiscoveryService.errorLoadingScript * @param {string} url - URL. @@ -355,6 +406,10 @@ protected slots: bool _defaultScriptsLocationOverridden { false }; QString _debugScriptUrl; + // TODO: remove script managers when shutting them down + QSet _scriptManagersThatRequestedServerEntityMessages; + QMutex _serverEntityMessagesMutex; + // If this is set, defaultScripts.js will not be run if it is in the settings, // and this will be run instead. This script will not be persisted to settings. const QUrl _defaultScriptsOverride { }; diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index ccdc917f0b4..0509978a749 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -584,7 +584,7 @@ void ScriptManager::scriptErrorMessage(const QString& message, const QString& fi qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit errorMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - emit errorEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); + emit errorEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript()); } } @@ -592,7 +592,7 @@ void ScriptManager::scriptWarningMessage(const QString& message, const QString& qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit warningMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - emit warningEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); + emit warningEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript()); } } @@ -600,7 +600,7 @@ void ScriptManager::scriptInfoMessage(const QString& message, const QString& fil qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit infoMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - emit infoEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); + emit infoEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript()); } } @@ -608,7 +608,7 @@ void ScriptManager::scriptPrintedMessage(const QString& message, const QString& qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit printedMessage(message, getFilename()); if (!currentEntityIdentifier.isInvalidID()) { - emit printedEntityMessage(message, fileName, lineNumber, currentEntityIdentifier); + emit printedEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript()); } } diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h index 0a25f9e2e36..623b51a43f1 100644 --- a/libraries/script-engine/src/ScriptManager.h +++ b/libraries/script-engine/src/ScriptManager.h @@ -1336,8 +1336,9 @@ public slots: * @param fileName Name of the file in which message was generated. * @param lineNumber Number of the line on which message was generated. * @param entityID + * @param isServerScript true if entity script is server-side, false if it is client-side. */ - void printedEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + void printedEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); /** @@ -1347,8 +1348,9 @@ public slots: * @param fileName Name of the file in which message was generated. * @param lineNumber Number of the line on which message was generated. * @param entityID + * @param isServerScript true if entity script is server-side, false if it is client-side. */ - void errorEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + void errorEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); @@ -1359,8 +1361,9 @@ public slots: * @param fileName Name of the file in which message was generated. * @param lineNumber Number of the line on which message was generated. * @param entityID + * @param isServerScript true if entity script is server-side, false if it is client-side. */ - void warningEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + void warningEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); /** @@ -1370,8 +1373,9 @@ public slots: * @param fileName Name of the file in which message was generated. * @param lineNumber Number of the line on which message was generated. * @param entityID + * @param isServerScript true if entity script is server-side, false if it is client-side. */ - void infoEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); + void infoEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript); /** diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp index 5ba59a26deb..d41b53660d5 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp @@ -12,6 +12,7 @@ #include "ScriptManager.h" #include "ScriptManagerScriptingInterface.h" +#include "ScriptEngines.h" #include "ScriptEngine.h" #include @@ -35,10 +36,6 @@ connect(_manager, &ScriptManager::printedMessage, this, &ScriptManagerScriptingInterface::printedMessage); connect(_manager, &ScriptManager::errorMessage, this, &ScriptManagerScriptingInterface::errorMessage); connect(_manager, &ScriptManager::warningMessage, this, &ScriptManagerScriptingInterface::warningMessage); - connect(_manager, &ScriptManager::infoEntityMessage, this, &ScriptManagerScriptingInterface::infoEntityMessage); - connect(_manager, &ScriptManager::printedEntityMessage, this, &ScriptManagerScriptingInterface::printedEntityMessage); - connect(_manager, &ScriptManager::errorEntityMessage, this, &ScriptManagerScriptingInterface::errorEntityMessage); - connect(_manager, &ScriptManager::warningEntityMessage, this, &ScriptManagerScriptingInterface::warningEntityMessage); connect(_manager, &ScriptManager::infoMessage, this, &ScriptManagerScriptingInterface::infoMessage); connect(_manager, &ScriptManager::runningStateChanged, this, &ScriptManagerScriptingInterface::runningStateChanged); connect(_manager, &ScriptManager::clearDebugWindow, this, &ScriptManagerScriptingInterface::clearDebugWindow); @@ -92,3 +89,18 @@ void ScriptManagerScriptingInterface::startProfiling() { void ScriptManagerScriptingInterface::stopProfilingAndSave() { _manager->engine()->stopProfilingAndSave(); } + +void ScriptManagerScriptingInterface::requestServerEntityScriptMessages() { + if (_manager->isEntityServerScript() || _manager->isEntityServerScript() || _manager->isClientScript()) { + auto scriptEngines = DependencyManager::get().data(); + //TODO; + } +} + +void ScriptManagerScriptingInterface::removeServerEntityScriptMessagesRequest() { + if (_manager->isEntityServerScript() || _manager->isEntityServerScript() || _manager->isClientScript()) { + auto scriptEngines = DependencyManager::get().data(); + //TODO; + } +} + diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.h b/libraries/script-engine/src/ScriptManagerScriptingInterface.h index d25592d42e9..13cacb07fb6 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.h +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.h @@ -557,7 +557,25 @@ class ScriptManagerScriptingInterface : public QObject { */ Q_INVOKABLE void stopProfilingAndSave(); -signals: + /*@jsdoc + * After calling this function current script engine will start receiving server-side entity script messages + * through signals such as errorEntityMessage. This function can be invoked both from client-side entity scripts + * and from interface scripts. + * @function Script.subscribeToServerEntityScriptMessages + */ + + Q_INVOKABLE void requestServerEntityScriptMessages(); + + /*@jsdoc + * Calling this function signalizes that current script doesn't require stop receiving server-side entity script messages + * through signals such as errorEntityMessage. This function can be invoked both from client-side entity scripts + * and from interface scripts. + * @function Script.unsubscribeFromServerEntityScriptMessages + */ + + Q_INVOKABLE void removeServerEntityScriptMessagesRequest(); + + signals: /*@jsdoc * @function Script.scriptLoaded @@ -644,53 +662,6 @@ class ScriptManagerScriptingInterface : public QObject { */ void infoMessage(const QString& message, const QString& scriptName); - /*@jsdoc - * Triggered when a client side entity script prints a message to the program log via {@link print}, {@link Script.print}, - * {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or - * {@link console.timeEnd}. - * @function Script.printedMessage - * @param {string} message - The message. - * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. - * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. - * @param {Uuid} entityID - Entity ID. - * @returns {Signal} - */ - void printedEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); - - /*@jsdoc - * Triggered when a client side entity script generates an error, {@link console.error} or {@link console.exception} is called, or - * {@link console.assert} is called and fails. - * @function Script.errorMessage - * @param {string} message - The error message. - * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. - * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. - * @param {Uuid} entityID - Entity ID. - * @returns {Signal} - */ - void errorEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); - - /*@jsdoc - * Triggered when a client side entity script generates a warning or {@link console.warn} is called. - * @function Script.warningMessage - * @param {string} message - The warning message. - * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. - * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. - * @param {Uuid} entityID - Entity ID. - * @returns {Signal} - */ - void warningEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); - - /*@jsdoc - * Triggered when a client side entity script generates an information message or {@link console.info} is called. - * @function Script.infoMessage - * @param {string} message - The information message. - * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available. - * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available. - * @param {Uuid} entityID - Entity ID. - * @returns {Signal} - */ - void infoEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID); - /*@jsdoc * Triggered when the running state of the script changes, e.g., from running to stopping. * @function Script.runningStateChanged diff --git a/libraries/script-engine/src/ScriptMessage.cpp b/libraries/script-engine/src/ScriptMessage.cpp index cb920fa5691..27ba05f31ea 100644 --- a/libraries/script-engine/src/ScriptMessage.cpp +++ b/libraries/script-engine/src/ScriptMessage.cpp @@ -18,6 +18,7 @@ QJsonObject ScriptMessage::toJson() { object["message"] = _messageContent; object["lineNumber"] = _lineNumber; object["fileName"] = _fileName; + object["entityID"] = _entityID.toString(); object["type"] = static_cast(_scriptType); object["severity"] = static_cast(_severity); return object; @@ -27,6 +28,7 @@ bool ScriptMessage::fromJson(const QJsonObject &object) { if (!object["message"].isString() || !object["lineNumber"].isDouble() || !object["fileName"].isString() + || !object["entityID"].isString() || !object["type"].isDouble() || !object["severity"].isDouble()) { qDebug() << "ScriptMessage::fromJson failed to find required fields in JSON file"; @@ -35,6 +37,7 @@ bool ScriptMessage::fromJson(const QJsonObject &object) { _messageContent = object["message"].toString(); _lineNumber = object["lineNumber"].toInt(); _fileName = object["fileName"].toInt(); + _entityID = QUuid::fromString(object["entityID"].toString()); _scriptType = static_cast(object["type"].toInt()); _severity = static_cast(object["severity"].toInt()); } \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptMessage.h b/libraries/script-engine/src/ScriptMessage.h index a0a9f52615c..6fa7892d081 100644 --- a/libraries/script-engine/src/ScriptMessage.h +++ b/libraries/script-engine/src/ScriptMessage.h @@ -18,6 +18,7 @@ #include #include +#include "EntityItemID.h" class ScriptMessage { public: @@ -35,16 +36,24 @@ class ScriptMessage { }; ScriptMessage() {}; - ScriptMessage(QString messageContent, QString fileName, int lineNumber, ScriptType scriptType, Severity severity) - : _messageContent(messageContent), _fileName(fileName), _lineNumber(lineNumber), _scriptType(scriptType) {} + ScriptMessage(const QString &messageContent, const QString &fileName, int lineNumber, const EntityItemID& entityID, ScriptType scriptType, Severity severity) + : _messageContent(messageContent), _fileName(fileName), _lineNumber(lineNumber), _entityID(entityID), _scriptType(scriptType), _severity(severity) {} QJsonObject toJson(); bool fromJson(const QJsonObject &object); + QString getMessage() { return _messageContent; } + QString getFileName() { return _fileName; } + int getLineNumber() { return _lineNumber; } + ScriptType getScriptType() { return _scriptType; } + Severity getSeverity() { return _severity; } + EntityItemID getEntityID() { return _entityID; } + private: QString _messageContent; QString _fileName; int _lineNumber {-1}; + EntityItemID _entityID; ScriptType _scriptType {ScriptType::TYPE_NONE}; Severity _severity {Severity::SEVERITY_NONE}; }; From d75efdb4caaa5c94fe5a09e477380c41b07fdf6c Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Wed, 10 Jan 2024 00:35:56 +0100 Subject: [PATCH 5/7] Script messaging fix --- libraries/script-engine/src/ScriptEngines.h | 2 +- libraries/script-engine/src/ScriptManager.cpp | 2 +- libraries/script-engine/src/ScriptMessage.cpp | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index d8d50a4c201..1a4e3c43891 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -251,7 +251,7 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize * Triggered when any script generates an information message or {@link console.info} is called. * @function ScriptDiscoveryService.infoMessage * @param {string} message - The information message. - * @param {string} scriptName - The name of the script that generated the informaton message. + * @param {string} scriptName - The name of the script that generated the information message. * @returns {Signal} */ void infoMessage(const QString& message, const QString& engineName); diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index 0509978a749..37ba7d04424 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -1317,7 +1317,7 @@ QUrl ScriptManager::resourcesPath() const { } void ScriptManager::print(const QString& message) { - emit printedMessage(message, getFilename()); + emit scriptPrintedMessage(message, getFilename(), engine()->currentContext()->currentLineNumber()); } diff --git a/libraries/script-engine/src/ScriptMessage.cpp b/libraries/script-engine/src/ScriptMessage.cpp index 27ba05f31ea..0b3ea1abc86 100644 --- a/libraries/script-engine/src/ScriptMessage.cpp +++ b/libraries/script-engine/src/ScriptMessage.cpp @@ -25,6 +25,10 @@ QJsonObject ScriptMessage::toJson() { } bool ScriptMessage::fromJson(const QJsonObject &object) { + if (object.isEmpty()) { + qDebug() << "ScriptMessage::fromJson object is empty"; + return false; + } if (!object["message"].isString() || !object["lineNumber"].isDouble() || !object["fileName"].isString() @@ -40,4 +44,5 @@ bool ScriptMessage::fromJson(const QJsonObject &object) { _entityID = QUuid::fromString(object["entityID"].toString()); _scriptType = static_cast(object["type"].toInt()); _severity = static_cast(object["severity"].toInt()); + return true; } \ No newline at end of file From c311d3cbfc0d9c432841585f0237d90f701af78e Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Mon, 29 Jan 2024 00:19:14 +0100 Subject: [PATCH 6/7] Script API for subscribing to entity server messages --- interface/src/Application.cpp | 5 ++ .../src/EntityScriptServerLogClient.cpp | 9 ++- .../src/EntityScriptServerLogClient.h | 9 +++ libraries/script-engine/src/ScriptEngines.cpp | 61 +++++++++++++++++++ libraries/script-engine/src/ScriptEngines.h | 21 ++++++- .../src/ScriptManagerScriptingInterface.cpp | 29 +++++++-- .../src/ScriptManagerScriptingInterface.h | 8 ++- 7 files changed, 132 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aa9d0a2f2f6..137457c7222 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -902,7 +902,12 @@ bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + auto scriptEngines = DependencyManager::get(); + auto entityScriptServerLog = DependencyManager::get(); + QObject::connect(scriptEngines.data(), &ScriptEngines::requestingEntityScriptServerLog, entityScriptServerLog.data(), &EntityScriptServerLogClient::requestMessagesForScriptEngines); + DependencyManager::set(); DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); DependencyManager::set(); diff --git a/libraries/entities/src/EntityScriptServerLogClient.cpp b/libraries/entities/src/EntityScriptServerLogClient.cpp index 5c21f017761..7329cf1fdd2 100644 --- a/libraries/entities/src/EntityScriptServerLogClient.cpp +++ b/libraries/entities/src/EntityScriptServerLogClient.cpp @@ -39,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()->getThisNodeCanRez()); - } else if (_subscribed && numReceivers == 0) { + } else if (_subscribed && (numReceivers == 0 && !_areMessagesRequestedByScripts)) { enableToEntityServerScriptLog(false); } } @@ -140,3 +140,8 @@ void EntityScriptServerLogClient::canRezChanged(bool canRez) { enableToEntityServerScriptLog(canRez); } } + +void EntityScriptServerLogClient::requestMessagesForScriptEngines(bool areMessagesRequested) { + _areMessagesRequestedByScripts = areMessagesRequested; + connectionsChanged(); +} diff --git a/libraries/entities/src/EntityScriptServerLogClient.h b/libraries/entities/src/EntityScriptServerLogClient.h index 9eee5daed87..e388de917ad 100644 --- a/libraries/entities/src/EntityScriptServerLogClient.h +++ b/libraries/entities/src/EntityScriptServerLogClient.h @@ -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 @@ -66,7 +72,10 @@ private slots: void connectionsChanged(); private: + std::atomic _areMessagesRequestedByScripts {false}; bool _subscribed { false }; + + friend class ScriptEngines; }; #endif // hifi_EntityScriptServerLogClient_h diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index b47255ed24f..52d3b2753bd 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -158,8 +158,69 @@ void ScriptEngines::removeScriptEngine(ScriptManagerPointer manager) { QMutexLocker locker(&_allScriptsMutex); _allKnownScriptManagers.remove(manager); } + std::lock_guard lock(_subscriptionsToEntityScriptMessagesMutex); + _managersSubscribedToEntityScriptMessages.remove(manager.get()); + _entitiesSubscribedToEntityScriptMessages.remove(manager.get()); +} + +void ScriptEngines::requestServerEntityScriptMessages(ScriptManager *manager) { + std::lock_guard lock(_subscriptionsToEntityScriptMessagesMutex); + if (!_managersSubscribedToEntityScriptMessages.contains(manager)) { + _managersSubscribedToEntityScriptMessages.insert(manager); + // Emit a signal to inform EntityScriptServerLogClient about subscription request + emit requestingEntityScriptServerLog(true); + qDebug() << "ScriptEngines::requestServerEntityScriptMessages"; + } +} + +void ScriptEngines::requestServerEntityScriptMessages(ScriptManager *manager, const QUuid& entityID) { + std::lock_guard lock(_subscriptionsToEntityScriptMessagesMutex); + if (!_entitiesSubscribedToEntityScriptMessages.contains(manager)) { + _entitiesSubscribedToEntityScriptMessages.insert(manager,QSet()); + } + if (!_entitiesSubscribedToEntityScriptMessages[manager].contains(entityID)) { + _entitiesSubscribedToEntityScriptMessages[manager].insert(entityID); + // Emit a signal to inform EntityScriptServerLogClient about subscription request + emit requestingEntityScriptServerLog(true); + qDebug() << "ScriptEngines::requestServerEntityScriptMessages uuid"; + } +} + +void ScriptEngines::removeServerEntityScriptMessagesRequest(ScriptManager *manager) { + std::lock_guard lock(_subscriptionsToEntityScriptMessagesMutex); + if (_managersSubscribedToEntityScriptMessages.contains(manager)) { + _managersSubscribedToEntityScriptMessages.remove(manager); + } + if (_entitiesSubscribedToEntityScriptMessages.isEmpty() + && _managersSubscribedToEntityScriptMessages.isEmpty()) { + // No managers requiring entity script server messages remain, so we inform EntityScriptServerLogClient about this + // Emit a signal to inform EntityScriptServerLogClient about subscription request + emit requestingEntityScriptServerLog(false); + qDebug() << "ScriptEngines::removeServerEntityScriptMessagesRequest"; + } } +void ScriptEngines::removeServerEntityScriptMessagesRequest(ScriptManager *manager, const QUuid& entityID) { + std::lock_guard lock(_subscriptionsToEntityScriptMessagesMutex); + if (!_entitiesSubscribedToEntityScriptMessages.contains(manager)) { + return; + } + if (_entitiesSubscribedToEntityScriptMessages[manager].contains(entityID)) { + _entitiesSubscribedToEntityScriptMessages[manager].remove(entityID); + } + if (_entitiesSubscribedToEntityScriptMessages[manager].isEmpty()) { + _entitiesSubscribedToEntityScriptMessages.remove(manager); + } + if (_entitiesSubscribedToEntityScriptMessages.isEmpty() + && _managersSubscribedToEntityScriptMessages.isEmpty()) { + // No managers requiring entity script server messages remain, so we inform EntityScriptServerLogClient about this + // Emit a signal to inform EntityScriptServerLogClient about subscription request + emit requestingEntityScriptServerLog(false); + qDebug() << "ScriptEngines::removeServerEntityScriptMessagesRequest uuid"; + } +} + + void ScriptEngines::shutdownScripting() { _isStopped = true; QMutexLocker locker(&_allScriptsMutex); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 1a4e3c43891..bafaa1322c1 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -186,6 +186,13 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize void removeScriptEngine(ScriptManagerPointer); + // Called by ScriptManagerScriptingInterface + void requestServerEntityScriptMessages(ScriptManager *manager); + void requestServerEntityScriptMessages(ScriptManager *manager, const QUuid& entityID); + + void removeServerEntityScriptMessagesRequest(ScriptManager *manager); + void removeServerEntityScriptMessagesRequest(ScriptManager *manager, const QUuid& entityID); + ScriptGatekeeper scriptGatekeeper; signals: @@ -323,6 +330,12 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize */ void clearDebugWindow(); + /** + * @brief Fires when script engines need entity server script messages (areMessagesRequested == true) + * and when messages are not needed anymore (areMessagesRequested == false). + */ + void requestingEntityScriptServerLog(bool areMessagesRequested); + public slots: /*@jsdoc @@ -406,9 +419,11 @@ protected slots: bool _defaultScriptsLocationOverridden { false }; QString _debugScriptUrl; - // TODO: remove script managers when shutting them down - QSet _scriptManagersThatRequestedServerEntityMessages; - QMutex _serverEntityMessagesMutex; + // For subscriptions to server entity script messages + std::mutex _subscriptionsToEntityScriptMessagesMutex; + QSet _managersSubscribedToEntityScriptMessages; + // Since multiple entity scripts run in the same script engine, there's a need to track subscriptions per entity + QHash> _entitiesSubscribedToEntityScriptMessages; // If this is set, defaultScripts.js will not be run if it is in the settings, // and this will be run instead. This script will not be persisted to settings. diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp index d41b53660d5..8c2d2799f3d 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp @@ -91,16 +91,37 @@ void ScriptManagerScriptingInterface::stopProfilingAndSave() { } void ScriptManagerScriptingInterface::requestServerEntityScriptMessages() { - if (_manager->isEntityServerScript() || _manager->isEntityServerScript() || _manager->isClientScript()) { + if (_manager->isEntityServerScript() || _manager->isEntityServerScript()) { + _manager->engine()->raiseException("Uuid needs to be specified when requestServerEntityScriptMessages is invoked from entity script"); + } else { auto scriptEngines = DependencyManager::get().data(); - //TODO; + scriptEngines->requestServerEntityScriptMessages(_manager); + } +} + +void ScriptManagerScriptingInterface::requestServerEntityScriptMessages(const QUuid& entityID) { + if (_manager->isEntityServerScript() || _manager->isEntityServerScript()) { + auto scriptEngines = DependencyManager::get().data(); + scriptEngines->requestServerEntityScriptMessages(_manager, entityID); + } else { + _manager->engine()->raiseException("Uuid must not be specified when requestServerEntityScriptMessages is invoked from entity script"); } } void ScriptManagerScriptingInterface::removeServerEntityScriptMessagesRequest() { - if (_manager->isEntityServerScript() || _manager->isEntityServerScript() || _manager->isClientScript()) { + if (_manager->isEntityServerScript() || _manager->isEntityServerScript()) { + _manager->engine()->raiseException("Uuid needs to be specified when removeServerEntityScriptMessagesRequest is invoked from entity script"); + } else { auto scriptEngines = DependencyManager::get().data(); - //TODO; + scriptEngines->removeServerEntityScriptMessagesRequest(_manager); } } +void ScriptManagerScriptingInterface::removeServerEntityScriptMessagesRequest(const QUuid& entityID) { + if (_manager->isEntityServerScript() || _manager->isEntityServerScript()) { + auto scriptEngines = DependencyManager::get().data(); + scriptEngines->removeServerEntityScriptMessagesRequest(_manager, entityID); + } else { + _manager->engine()->raiseException("Uuid must not be specified when removeServerEntityScriptMessagesRequest is invoked from entity script"); + } +} diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.h b/libraries/script-engine/src/ScriptManagerScriptingInterface.h index 13cacb07fb6..c1d6bad360d 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.h +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.h @@ -512,7 +512,7 @@ class ScriptManagerScriptingInterface : public QObject { /*@jsdoc * Start collecting object statistics that can later be reported with Script.dumpHeapObjectStatistics(). - * @function Script.dumpHeapObjectStatistics + * @function Script.startCollectingObjectStatistics */ Q_INVOKABLE void startCollectingObjectStatistics(); @@ -562,18 +562,24 @@ class ScriptManagerScriptingInterface : public QObject { * through signals such as errorEntityMessage. This function can be invoked both from client-side entity scripts * and from interface scripts. * @function Script.subscribeToServerEntityScriptMessages + * @param {Uuid=} entityID - The ID of the entity that requests entity server script messages. Only needs to be specified + * for entity scripts, and must not be specified for other types of scripts. */ Q_INVOKABLE void requestServerEntityScriptMessages(); + Q_INVOKABLE void requestServerEntityScriptMessages(const QUuid& entityID); /*@jsdoc * Calling this function signalizes that current script doesn't require stop receiving server-side entity script messages * through signals such as errorEntityMessage. This function can be invoked both from client-side entity scripts * and from interface scripts. * @function Script.unsubscribeFromServerEntityScriptMessages + * @param {Uuid=} entityID - The ID of the entity that requests entity server script messages. Only needs to be specified + * for entity scripts, and must not be specified for other types of scripts. */ Q_INVOKABLE void removeServerEntityScriptMessagesRequest(); + Q_INVOKABLE void removeServerEntityScriptMessagesRequest(const QUuid& entityID); signals: From 5c5c529fa97a8b89495c046593db4bb48505d88c Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Sat, 17 Feb 2024 14:32:18 +0100 Subject: [PATCH 7/7] Fix buffer size --- assignment-client/src/scripts/EntityScriptServer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 59302d3696c..2b23434195b 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -226,11 +226,13 @@ void EntityScriptServer::pushLogs() { 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(data.toStdString().c_str(), data.size()); + packet->write(cstring, strlen(cstring)); nodeList->sendPacketList(std::move(packet), *node); } }