Merge pull request #673 from overte-org/feature/entity_script_logging

Entity script logging for script editor
This commit is contained in:
ksuprynowicz 2024-02-17 16:32:05 +01:00 committed by GitHub
commit 1fd3f80210
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 747 additions and 65 deletions

View file

@ -44,16 +44,8 @@
using Mutex = std::mutex;
using Lock = std::lock_guard<Mutex>;
static std::mutex logBufferMutex;
static std::string logBuffer;
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
auto logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message);
if (!logMessage.isEmpty()) {
Lock lock(logBufferMutex);
logBuffer.append(logMessage.toStdString() + '\n');
}
}
int EntityScriptServer::_entitiesScriptEngineCount = 0;
@ -217,10 +209,10 @@ void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<Receiv
}
void EntityScriptServer::pushLogs() {
std::string buffer;
QJsonArray buffer;
{
Lock lock(logBufferMutex);
std::swap(logBuffer, buffer);
Lock lock(_logBufferMutex);
std::swap(_logBuffer, buffer);
}
if (buffer.empty()) {
@ -231,16 +223,27 @@ void EntityScriptServer::pushLogs() {
}
auto nodeList = DependencyManager::get<NodeList>();
QJsonDocument document;
document.setArray(buffer);
QString data(document.toJson());
std::string string = data.toStdString();
auto cstring = string.c_str();
for (auto uuid : _logListeners) {
auto node = nodeList->nodeWithUUID(uuid);
if (node && node->getActiveSocket()) {
auto packet = NLPacketList::create(PacketType::EntityServerScriptLog, QByteArray(), true, true);
packet->write(buffer.data(), buffer.size());
packet->write(cstring, strlen(cstring));
nodeList->sendPacketList(std::move(packet), *node);
}
}
}
void EntityScriptServer::addLogEntry(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, ScriptMessage::Severity severity) {
ScriptMessage entry(message, fileName, lineNumber, entityID, ScriptMessage::ScriptType::TYPE_ENTITY_SCRIPT, severity);
Lock lock(_logBufferMutex);
_logBuffer.append(entry.toJson());
}
void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode) {
if (_entitiesScriptManager && _entityViewer.getTree() && !_shuttingDown) {
@ -469,6 +472,29 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
connect(newManager.get(), &ScriptManager::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage);
connect(newManager.get(), &ScriptManager::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage);
// Make script engine messages available through ScriptDiscoveryService
connect(newManager.get(), &ScriptManager::infoEntityMessage, scriptEngines, &ScriptEngines::infoEntityMessage);
connect(newManager.get(), &ScriptManager::printedEntityMessage, scriptEngines, &ScriptEngines::printedEntityMessage);
connect(newManager.get(), &ScriptManager::errorEntityMessage, scriptEngines, &ScriptEngines::errorEntityMessage);
connect(newManager.get(), &ScriptManager::warningEntityMessage, scriptEngines, &ScriptEngines::warningEntityMessage);
connect(newManager.get(), &ScriptManager::infoEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_INFO);
});
connect(newManager.get(), &ScriptManager::printedEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_PRINT);
});
connect(newManager.get(), &ScriptManager::errorEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_ERROR);
});
connect(newManager.get(), &ScriptManager::warningEntityMessage,
[this](const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID) {
addLogEntry(message, fileName, lineNumber, entityID, ScriptMessage::Severity::SEVERITY_WARNING);
});
connect(newManager.get(), &ScriptManager::update, this, [this] {
_entityViewer.queryOctree();
_entityViewer.getTree()->preUpdate();

View file

@ -27,6 +27,8 @@
#include <SimpleEntitySimulation.h>
#include <ThreadedAssignment.h>
#include <ScriptManager.h>
#include <ScriptMessage.h>
#include <QJsonArray>
#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<ReceivedMessage> message, SharedNodePointer senderNode);
/**
* @brief Transmit logs
*
* This is called periodically through a timer to transmit logs from scripts.
*/
void pushLogs();
/**
* @brief Adds log entry to the transmit buffer
*
* This is connected to entity script log events in the script manager and adds script log message to the buffer
* containing messages that will be sent to subscribed clients.
*/
void addLogEntry(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, ScriptMessage::Severity severity);
void handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
@ -85,6 +109,9 @@ private:
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
QJsonArray _logBuffer;
std::mutex _logBufferMutex;
int _maxEntityPPS { DEFAULT_MAX_ENTITY_PPS };
int _entityPPSPerScript { DEFAULT_ENTITY_PPS_PER_SCRIPT };

View file

@ -902,7 +902,12 @@ bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted
DependencyManager::set<CompositorHelper>();
DependencyManager::set<OffscreenQmlSurfaceCache>();
DependencyManager::set<EntityScriptClient>();
DependencyManager::set<EntityScriptServerLogClient>();
auto scriptEngines = DependencyManager::get<ScriptEngines>();
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
QObject::connect(scriptEngines.data(), &ScriptEngines::requestingEntityScriptServerLog, entityScriptServerLog.data(), &EntityScriptServerLogClient::requestMessagesForScriptEngines);
DependencyManager::set<GooglePolyScriptingInterface>();
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
DependencyManager::set<AvatarBookmarks>();

View file

@ -232,6 +232,14 @@ void EntityTreeRenderer::resetPersistentEntitiesScriptEngine() {
_persistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_persistentEntitiesScriptManager);
// Make script engine messages available through ScriptDiscoveryService
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::infoEntityMessage, scriptEngines, &ScriptEngines::infoEntityMessage);
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::printedEntityMessage, scriptEngines, &ScriptEngines::printedEntityMessage);
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::errorEntityMessage, scriptEngines, &ScriptEngines::errorEntityMessage);
connect(_persistentEntitiesScriptManager.get(), &ScriptManager::warningEntityMessage, scriptEngines, &ScriptEngines::warningEntityMessage);
_persistentEntitiesScriptManager->runInThread();
std::shared_ptr<EntitiesScriptEngineProvider> entitiesScriptEngineProvider = _persistentEntitiesScriptManager;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
@ -255,6 +263,14 @@ void EntityTreeRenderer::resetNonPersistentEntitiesScriptEngine() {
_nonPersistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_nonPersistentEntitiesScriptManager);
// Make script engine messages available through ScriptDiscoveryService
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::infoEntityMessage, scriptEngines, &ScriptEngines::infoEntityMessage);
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::printedEntityMessage, scriptEngines, &ScriptEngines::printedEntityMessage);
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::errorEntityMessage, scriptEngines, &ScriptEngines::errorEntityMessage);
connect(_nonPersistentEntitiesScriptManager.get(), &ScriptManager::warningEntityMessage, scriptEngines, &ScriptEngines::warningEntityMessage);
_nonPersistentEntitiesScriptManager->runInThread();
std::shared_ptr<EntitiesScriptEngineProvider> entitiesScriptEngineProvider = _nonPersistentEntitiesScriptManager;
DependencyManager::get<EntityScriptingInterface>()->setNonPersistentEntitiesScriptEngine(entitiesScriptEngineProvider);

View file

@ -9,7 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QJsonDocument>
#include <QJsonArray>
#include "EntityScriptServerLogClient.h"
#include "ScriptMessage.h"
#include "ScriptEngines.h"
EntityScriptServerLogClient::EntityScriptServerLogClient() {
auto nodeList = DependencyManager::get<NodeList>();
@ -35,9 +39,9 @@ void EntityScriptServerLogClient::disconnectNotify(const QMetaMethod& signal) {
void EntityScriptServerLogClient::connectionsChanged() {
auto numReceivers = receivers(SIGNAL(receivedNewLogLines(QString)));
if (!_subscribed && numReceivers > 0) {
if (!_subscribed && (numReceivers > 0 || _areMessagesRequestedByScripts)) {
enableToEntityServerScriptLog(DependencyManager::get<NodeList>()->getThisNodeCanRez());
} else if (_subscribed && numReceivers == 0) {
} else if (_subscribed && (numReceivers == 0 && !_areMessagesRequestedByScripts)) {
enableToEntityServerScriptLog(false);
}
}
@ -62,7 +66,59 @@ void EntityScriptServerLogClient::enableToEntityServerScriptLog(bool enable) {
}
void EntityScriptServerLogClient::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
emit receivedNewLogLines(QString::fromUtf8(message->readAll()));
QString messageText = QString::fromUtf8(message->readAll());
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(messageText.toUtf8(), &error);
emit receivedNewLogLines(messageText);
if(document.isNull()) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: Cannot parse JSON: " << error.errorString()
<< " Contents: " << messageText;
return;
}
// Iterate through contents and emit messages
if(!document.isArray()) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: JSON is not an array: " << messageText;
return;
}
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
auto array = document.array();
for (int n = 0; n < array.size(); n++) {
if (!array[n].isObject()) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: message is not an object: " << messageText;
continue;
}
ScriptMessage scriptMessage;
if (!scriptMessage.fromJson(array[n].toObject())) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: message parsing failed: " << messageText;
continue;
}
switch (scriptMessage.getSeverity()) {
case ScriptMessage::Severity::SEVERITY_INFO:
emit scriptEngines->infoEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;
case ScriptMessage::Severity::SEVERITY_PRINT:
emit scriptEngines->printedEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;
case ScriptMessage::Severity::SEVERITY_WARNING:
emit scriptEngines->warningEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;
case ScriptMessage::Severity::SEVERITY_ERROR:
emit scriptEngines->errorEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
break;
default:
break;
}
}
}
void EntityScriptServerLogClient::nodeActivated(SharedNodePointer activatedNode) {
@ -84,3 +140,8 @@ void EntityScriptServerLogClient::canRezChanged(bool canRez) {
enableToEntityServerScriptLog(canRez);
}
}
void EntityScriptServerLogClient::requestMessagesForScriptEngines(bool areMessagesRequested) {
_areMessagesRequestedByScripts = areMessagesRequested;
connectionsChanged();
}

View file

@ -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<bool> _areMessagesRequestedByScripts {false};
bool _subscribed { false };
friend class ScriptEngines;
};
#endif // hifi_EntityScriptServerLogClient_h

View file

@ -34,7 +34,7 @@ QList<QString> ConsoleScriptingInterface::_groupDetails = QList<QString>();
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();
}
@ -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());
}
}

View file

@ -57,6 +57,13 @@ public:
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;

View file

@ -158,8 +158,69 @@ void ScriptEngines::removeScriptEngine(ScriptManagerPointer manager) {
QMutexLocker locker(&_allScriptsMutex);
_allKnownScriptManagers.remove(manager);
}
std::lock_guard<std::mutex> lock(_subscriptionsToEntityScriptMessagesMutex);
_managersSubscribedToEntityScriptMessages.remove(manager.get());
_entitiesSubscribedToEntityScriptMessages.remove(manager.get());
}
void ScriptEngines::requestServerEntityScriptMessages(ScriptManager *manager) {
std::lock_guard<std::mutex> 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<std::mutex> lock(_subscriptionsToEntityScriptMessagesMutex);
if (!_entitiesSubscribedToEntityScriptMessages.contains(manager)) {
_entitiesSubscribedToEntityScriptMessages.insert(manager,QSet<QUuid>());
}
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<std::mutex> 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<std::mutex> 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);

View file

@ -186,6 +186,13 @@ public:
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:
@ -251,11 +258,62 @@ signals:
* 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);
/*@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.
@ -272,6 +330,12 @@ signals:
*/
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
@ -355,6 +419,12 @@ protected:
bool _defaultScriptsLocationOverridden { false };
QString _debugScriptUrl;
// For subscriptions to server entity script messages
std::mutex _subscriptionsToEntityScriptMessagesMutex;
QSet<ScriptManager*> _managersSubscribedToEntityScriptMessages;
// Since multiple entity scripts run in the same script engine, there's a need to track subscriptions per entity
QHash<ScriptManager*, QSet<QUuid>> _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.
const QUrl _defaultScriptsOverride { };

View file

@ -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,24 +580,36 @@ 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()) {
emit errorEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
}
}
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()) {
emit warningEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
}
}
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()) {
emit infoEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
}
}
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()) {
emit printedEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
}
}
void ScriptManager::clearDebugLogWindow() {
@ -912,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();
@ -1064,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();
@ -1139,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
}
@ -1206,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
}
@ -1215,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
}
@ -1281,7 +1317,7 @@ QUrl ScriptManager::resourcesPath() const {
}
void ScriptManager::print(const QString& message) {
emit printedMessage(message, getFilename());
emit scriptPrintedMessage(message, getFilename(), engine()->currentContext()->currentLineNumber());
}
@ -1651,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<QUrl> urls;
@ -1665,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;
@ -1676,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.
@ -1699,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<std::recursive_mutex> lock(_lock);
if (!_includedURLs.contains(url)) {
@ -1719,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);
}
}
}
@ -1748,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
}
@ -1765,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
}
@ -2440,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);
}
}

View file

@ -1074,8 +1074,10 @@ public:
* 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 @@ public:
* 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 @@ public:
* 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 @@ public:
* 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,54 @@ signals:
*/
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
* @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, bool isServerScript);
/**
* @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
* @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, bool isServerScript);
/**
* @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
* @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, bool isServerScript);
/**
* @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
* @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, bool isServerScript);
/**
* @brief Triggered when the running state of the script changes, e.g., from running to stopping.

View file

@ -12,6 +12,7 @@
#include "ScriptManager.h"
#include "ScriptManagerScriptingInterface.h"
#include "ScriptEngines.h"
#include "ScriptEngine.h"
#include <QMetaType>
@ -88,3 +89,39 @@ void ScriptManagerScriptingInterface::startProfiling() {
void ScriptManagerScriptingInterface::stopProfilingAndSave() {
_manager->engine()->stopProfilingAndSave();
}
void ScriptManagerScriptingInterface::requestServerEntityScriptMessages() {
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<ScriptEngines>().data();
scriptEngines->requestServerEntityScriptMessages(_manager);
}
}
void ScriptManagerScriptingInterface::requestServerEntityScriptMessages(const QUuid& entityID) {
if (_manager->isEntityServerScript() || _manager->isEntityServerScript()) {
auto scriptEngines = DependencyManager::get<ScriptEngines>().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->engine()->raiseException("Uuid needs to be specified when removeServerEntityScriptMessagesRequest is invoked from entity script");
} else {
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
scriptEngines->removeServerEntityScriptMessagesRequest(_manager);
}
}
void ScriptManagerScriptingInterface::removeServerEntityScriptMessagesRequest(const QUuid& entityID) {
if (_manager->isEntityServerScript() || _manager->isEntityServerScript()) {
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
scriptEngines->removeServerEntityScriptMessagesRequest(_manager, entityID);
} else {
_manager->engine()->raiseException("Uuid must not be specified when removeServerEntityScriptMessagesRequest is invoked from entity script");
}
}

View file

@ -512,7 +512,7 @@ public:
/*@jsdoc
* Start collecting object statistics that can later be reported with Script.dumpHeapObjectStatistics().
* @function Script.dumpHeapObjectStatistics
* @function Script.startCollectingObjectStatistics
*/
Q_INVOKABLE void startCollectingObjectStatistics();
@ -557,7 +557,31 @@ public:
*/
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
* @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:
/*@jsdoc
* @function Script.scriptLoaded

View file

@ -0,0 +1,48 @@
//
// 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"
#include <qhash.h>
QJsonObject ScriptMessage::toJson() {
QJsonObject object;
object["message"] = _messageContent;
object["lineNumber"] = _lineNumber;
object["fileName"] = _fileName;
object["entityID"] = _entityID.toString();
object["type"] = static_cast<int>(_scriptType);
object["severity"] = static_cast<int>(_severity);
return object;
}
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()
|| !object["entityID"].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();
_entityID = QUuid::fromString(object["entityID"].toString());
_scriptType = static_cast<ScriptMessage::ScriptType>(object["type"].toInt());
_severity = static_cast<ScriptMessage::Severity>(object["severity"].toInt());
return true;
}

View file

@ -0,0 +1,61 @@
//
// 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 <QString>
#include <QJsonObject>
#include "EntityItemID.h"
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
};
ScriptMessage() {};
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};
};
#endif //OVERTE_SCRIPTMESSAGE_H

View file

@ -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<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(isolate, 1);
if (stackTrace->GetFrameCount() > 0) {
v8::Local<v8::StackFrame> 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<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(isolate, 1);
QStringList backTrace;
if (stackTrace->GetFrameCount() > 0) {
v8::Local<v8::StackFrame> stackFrame = stackTrace->GetFrame(isolate, 0);
return *v8::String::Utf8Value(isolate, stackFrame->GetScriptNameOrSourceURL());
} else {
return "";
}
}
ScriptValue ScriptContextV8Wrapper::callee() const {
Q_ASSERT(false);
//V8TODO

View file

@ -45,6 +45,13 @@ public: // ScriptContext implementation
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;

View file

@ -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<v8::Context> &context ) {
v8::Local<v8::Message> exceptionMessage = tryCatch.Message();
QString errorFileName;
auto resource = exceptionMessage->GetScriptResourceName();
v8::Local<v8::String> 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<v8::Message> 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<v8::Message> 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<v8::Message> 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;
}

View file

@ -309,6 +309,8 @@ private:
ScriptEngineV8* _engine;
};
QString getFileNameFromTryCatch(v8::TryCatch &tryCatch, v8::Isolate *isolate, v8::Local<v8::Context> &context );
#include "V8Types.h"
#endif // hifi_ScriptEngineV8_h

View file

@ -56,6 +56,13 @@ public: // ScriptContext implementation
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(); }
@ -1274,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<v8::Message> 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;
}

View file

@ -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<v8::Message> 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;
}