Merge pull request #6182 from Atlante45/script-engine

Improved ScriptEngine debug messages
This commit is contained in:
Brad Hefta-Gaub 2015-10-28 11:27:30 -07:00
commit c5e22a62e8
6 changed files with 102 additions and 75 deletions

View file

@ -564,12 +564,12 @@ CameraTool = function(cameraManager) {
var ORIENTATION_OVERLAY_SIZE = 26; var ORIENTATION_OVERLAY_SIZE = 26;
var ORIENTATION_OVERLAY_HALF_SIZE = ORIENTATION_OVERLAY_SIZE / 2; var ORIENTATION_OVERLAY_HALF_SIZE = ORIENTATION_OVERLAY_SIZE / 2;
var ORIENTATION_OVERLAY_CUBE_SIZE = 10.5, var ORIENTATION_OVERLAY_CUBE_SIZE = 10.5;
var ORIENTATION_OVERLAY_OFFSET = { var ORIENTATION_OVERLAY_OFFSET = {
x: 30, x: 30,
y: 30, y: 30,
} }
var UI_WIDTH = 70; var UI_WIDTH = 70;
var UI_HEIGHT = 70; var UI_HEIGHT = 70;

View file

@ -6,7 +6,7 @@ SoundArray = function(audioOptions, autoUpdateAudioPosition) {
this.audioOptions = audioOptions !== undefined ? audioOptions : {}; this.audioOptions = audioOptions !== undefined ? audioOptions : {};
this.autoUpdateAudioPosition = autoUpdateAudioPosition !== undefined ? autoUpdateAudioPosition : false; this.autoUpdateAudioPosition = autoUpdateAudioPosition !== undefined ? autoUpdateAudioPosition : false;
if (this.audioOptions.position === undefined) { if (this.audioOptions.position === undefined) {
this.audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0}), this.audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0});
} }
if (this.audioOptions.volume === undefined) { if (this.audioOptions.volume === undefined) {
this.audioOptions.volume = 1.0; this.audioOptions.volume = 1.0;

View file

@ -106,8 +106,11 @@ void JSConsole::executeCommand(const QString& command) {
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) { QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
QScriptValue result; QScriptValue result;
static const QString filename = "JSConcole";
QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection, QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result), Q_ARG(const QString&, command)); Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, command),
Q_ARG(const QString&, filename));
return result; return result;
} }

View file

@ -79,11 +79,41 @@ void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) {
QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) { QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) {
return engine->newQObject(in); return engine->newQObject(in);
} }
void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) { void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) {
out = qobject_cast<AbstractInputController*>(object.toQObject()); out = qobject_cast<AbstractInputController*>(object.toQObject());
} }
static bool hasCorrectSyntax(const QScriptProgram& program) {
const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
const auto error = syntaxCheck.errorMessage();
const auto line = QString::number(syntaxCheck.errorLineNumber());
const auto column = QString::number(syntaxCheck.errorColumnNumber());
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);
qCWarning(scriptengine) << qPrintable(message);
return false;
}
return true;
}
static bool hadUncauchtExceptions(QScriptEngine& engine, const QString& fileName) {
if (engine.hasUncaughtException()) {
const auto backtrace = engine.uncaughtExceptionBacktrace();
const auto exception = engine.uncaughtException().toString();
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
engine.clearExceptions();
auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line);
if (!backtrace.empty()) {
static const auto lineSeparator = "\n ";
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
}
qCWarning(scriptengine) << qPrintable(message);
return true;
}
return false;
}
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString,
AbstractControllerScriptingInterface* controllerScriptingInterface, bool wantSignals) : AbstractControllerScriptingInterface* controllerScriptingInterface, bool wantSignals) :
@ -95,9 +125,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
_wantSignals(wantSignals), _wantSignals(wantSignals),
_controllerScriptingInterface(controllerScriptingInterface), _controllerScriptingInterface(controllerScriptingInterface),
_fileNameString(fileNameString), _fileNameString(fileNameString),
_quatLibrary(),
_vec3Library(),
_uuidLibrary(),
_isUserLoaded(false), _isUserLoaded(false),
_isReloading(false), _isReloading(false),
_arrayBufferClass(new ArrayBufferClass(this)) _arrayBufferClass(new ArrayBufferClass(this))
@ -508,26 +535,33 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
// Connect up ALL the handlers to the global entities object's signals. // Connect up ALL the handlers to the global entities object's signals.
// (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
auto entities = DependencyManager::get<EntityScriptingInterface>(); auto entities = DependencyManager::get<EntityScriptingInterface>();
connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
[=](const EntityItemID& entityID) { _registeredHandlers.remove(entityID);
_registeredHandlers.remove(entityID); });
});
// Two common cases of event handler, differing only in argument signature. // Two common cases of event handler, differing only in argument signature.
auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&)> { using SingleEntityHandler = std::function<void(const EntityItemID&)>;
return [=](const EntityItemID& entityItemID) -> void { auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler {
generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { return [this, eventName](const EntityItemID& entityItemID) {
return QScriptValueList() << entityItemID.toScriptValue(this); forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) });
});
}; };
}; };
auto makeMouseHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&, const MouseEvent&)> {
return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void { using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>;
generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { auto makeMouseHandler = [this](QString eventName) -> MouseHandler {
return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) {
}); forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });
}; };
}; };
using CollisionHandler = std::function<void(const EntityItemID&, const EntityItemID&, const Collision&)>;
auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler {
return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) {
forwardHandlerCall(idA, eventName, { idA.toScriptValue(this), idB.toScriptValue(this),
collisionToScriptValue(this, collision) });
};
};
connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity"));
connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity"));
@ -543,12 +577,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity"));
connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity"));
[=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) {
generalHandler(idA, "collisionWithEntity", [=]() {
return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision);
});
});
} }
if (!_registeredHandlers.contains(entityID)) { if (!_registeredHandlers.contains(entityID)) {
_registeredHandlers[entityID] = RegisteredEventHandlers(); _registeredHandlers[entityID] = RegisteredEventHandlers();
@ -558,7 +587,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
} }
QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
if (_stoppingAllScripts) { if (_stoppingAllScripts) {
return QScriptValue(); // bail early return QScriptValue(); // bail early
} }
@ -567,27 +596,30 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN
QScriptValue result; QScriptValue result;
#ifdef THREAD_DEBUGGING #ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber; "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber;
#endif #endif
QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result), Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, program), Q_ARG(const QString&, sourceCode),
Q_ARG(const QString&, fileName), Q_ARG(const QString&, fileName),
Q_ARG(int, lineNumber)); Q_ARG(int, lineNumber));
return result; return result;
} }
// Check syntax
const QScriptProgram program(sourceCode, fileName, lineNumber);
if (!hasCorrectSyntax(program)) {
return QScriptValue();
}
_evaluatesPending++; ++_evaluatesPending;
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); const auto result = QScriptEngine::evaluate(program);
if (hasUncaughtException()) { --_evaluatesPending;
int line = uncaughtExceptionLineNumber();
qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString(); const auto hadUncaughtException = hadUncauchtExceptions(*this, program.fileName());
}
_evaluatesPending--;
if (_wantSignals) { if (_wantSignals) {
emit evaluationFinished(result, hasUncaughtException()); emit evaluationFinished(result, hadUncaughtException);
} }
clearExceptions();
return result; return result;
} }
@ -605,7 +637,7 @@ void ScriptEngine::run() {
emit runningStateChanged(); emit runningStateChanged();
} }
QScriptValue result = evaluate(_scriptContents); QScriptValue result = evaluate(_scriptContents, _fileNameString);
QElapsedTimer startTime; QElapsedTimer startTime;
startTime.start(); startTime.start();
@ -646,15 +678,6 @@ void ScriptEngine::run() {
qint64 now = usecTimestampNow(); qint64 now = usecTimestampNow();
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
if (hasUncaughtException()) {
int line = uncaughtExceptionLineNumber();
qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << uncaughtException().toString();
if (_wantSignals) {
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + uncaughtException().toString());
}
clearExceptions();
}
if (!_isFinished) { if (!_isFinished) {
if (_wantSignals) { if (_wantSignals) {
emit update(deltaTime); emit update(deltaTime);
@ -662,6 +685,9 @@ void ScriptEngine::run() {
} }
lastUpdate = now; lastUpdate = now;
if (hadUncauchtExceptions(*this, _fileNameString)) {
stop();
}
} }
stopAllTimers(); // make sure all our timers are stopped if the script is ending stopAllTimers(); // make sure all our timers are stopped if the script is ending
@ -899,9 +925,9 @@ void ScriptEngine::load(const QString& loadFile) {
} }
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) { void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
qDebug() << "*** ERROR *** ScriptEngine::generalHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
assert(false); assert(false);
return; return;
} }
@ -915,9 +941,8 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e
} }
QScriptValueList handlersForEvent = handlersOnEntity[eventName]; QScriptValueList handlersForEvent = handlersOnEntity[eventName];
if (!handlersForEvent.isEmpty()) { if (!handlersForEvent.isEmpty()) {
QScriptValueList args = argGenerator();
for (int i = 0; i < handlersForEvent.count(); ++i) { for (int i = 0; i < handlersForEvent.count(); ++i) {
handlersForEvent[i].call(QScriptValue(), args); handlersForEvent[i].call(QScriptValue(), eventHanderArgs);
} }
} }
} }
@ -981,14 +1006,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
auto scriptCache = DependencyManager::get<ScriptCache>(); auto scriptCache = DependencyManager::get<ScriptCache>();
bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); bool isFileUrl = isURL && scriptOrURL.startsWith("file://");
auto fileName = QString("(EntityID:%1, %2)").arg(entityID.toString(), isURL ? scriptOrURL : "EmbededEntityScript");
// first check the syntax of the script contents QScriptProgram program(contents, fileName);
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); if (!hasCorrectSyntax(program)) {
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID;
qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":"
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL;
if (!isFileUrl) { if (!isFileUrl) {
scriptCache->addScriptToBadScriptList(scriptOrURL); scriptCache->addScriptToBadScriptList(scriptOrURL);
} }
@ -1000,12 +1021,15 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
} }
QScriptEngine sandbox; QScriptEngine sandbox;
QScriptValue testConstructor = sandbox.evaluate(contents); QScriptValue testConstructor = sandbox.evaluate(program);
if (hadUncauchtExceptions(sandbox, program.fileName())) {
return;
}
if (!testConstructor.isFunction()) { if (!testConstructor.isFunction()) {
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n"
qCDebug(scriptengine) << " NOT CONSTRUCTOR"; " NOT CONSTRUCTOR\n"
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; " SCRIPT:" << scriptOrURL;
if (!isFileUrl) { if (!isFileUrl) {
scriptCache->addScriptToBadScriptList(scriptOrURL); scriptCache->addScriptToBadScriptList(scriptOrURL);
} }
@ -1018,7 +1042,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
} }
QScriptValue entityScriptConstructor = evaluate(contents); QScriptValue entityScriptConstructor = evaluate(contents, fileName);
QScriptValue entityScriptObject = entityScriptConstructor.construct(); QScriptValue entityScriptObject = entityScriptConstructor.construct();
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
_entityScripts[entityID] = newDetails; _entityScripts[entityID] = newDetails;

View file

@ -88,7 +88,7 @@ public:
Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value);
/// evaluate some code in the context of the ScriptEngine and return the result /// evaluate some code in the context of the ScriptEngine and return the result
Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1); // this is also used by the script tool widget Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget
/// if the script engine is not already running, this will download the URL and start the process of seting it up /// if the script engine is not already running, this will download the URL and start the process of seting it up
/// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed /// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed
@ -181,7 +181,7 @@ private:
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
void stopTimer(QTimer* timer); void stopTimer(QTimer* timer);
AbstractControllerScriptingInterface* _controllerScriptingInterface; AbstractControllerScriptingInterface* _controllerScriptingInterface;
QString _fileNameString; QString _fileNameString;
Quat _quatLibrary; Quat _quatLibrary;
@ -193,7 +193,7 @@ private:
ArrayBufferClass* _arrayBufferClass; ArrayBufferClass* _arrayBufferClass;
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers; QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator); void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
static QSet<ScriptEngine*> _allKnownScriptEngines; static QSet<ScriptEngine*> _allKnownScriptEngines;

View file

@ -135,7 +135,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
prefixString.append(QString(" [%1]").arg(_targetName)); prefixString.append(QString(" [%1]").arg(_targetName));
} }
QString logMessage = QString("%1 %2").arg(prefixString, message); QString logMessage = QString("%1 %2").arg(prefixString, message.split("\n").join("\n" + prefixString + " "));
fprintf(stdout, "%s\n", qPrintable(logMessage)); fprintf(stdout, "%s\n", qPrintable(logMessage));
return logMessage; return logMessage;
} }