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_HALF_SIZE = ORIENTATION_OVERLAY_SIZE / 2;
var ORIENTATION_OVERLAY_CUBE_SIZE = 10.5,
var ORIENTATION_OVERLAY_CUBE_SIZE = 10.5;
var ORIENTATION_OVERLAY_OFFSET = {
x: 30,
y: 30,
}
var ORIENTATION_OVERLAY_OFFSET = {
x: 30,
y: 30,
}
var UI_WIDTH = 70;
var UI_HEIGHT = 70;

View file

@ -6,7 +6,7 @@ SoundArray = function(audioOptions, autoUpdateAudioPosition) {
this.audioOptions = audioOptions !== undefined ? audioOptions : {};
this.autoUpdateAudioPosition = autoUpdateAudioPosition !== undefined ? autoUpdateAudioPosition : false;
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) {
this.audioOptions.volume = 1.0;

View file

@ -106,8 +106,11 @@ void JSConsole::executeCommand(const QString& command) {
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
QScriptValue result;
static const QString filename = "JSConcole";
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;
}

View file

@ -79,11 +79,41 @@ void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) {
QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) {
return engine->newQObject(in);
}
void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) {
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,
AbstractControllerScriptingInterface* controllerScriptingInterface, bool wantSignals) :
@ -95,9 +125,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
_wantSignals(wantSignals),
_controllerScriptingInterface(controllerScriptingInterface),
_fileNameString(fileNameString),
_quatLibrary(),
_vec3Library(),
_uuidLibrary(),
_isUserLoaded(false),
_isReloading(false),
_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.
// (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>();
connect(entities.data(), &EntityScriptingInterface::deletingEntity, this,
[=](const EntityItemID& entityID) {
_registeredHandlers.remove(entityID);
});
connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
_registeredHandlers.remove(entityID);
});
// Two common cases of event handler, differing only in argument signature.
auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&)> {
return [=](const EntityItemID& entityItemID) -> void {
generalHandler(entityItemID, eventName, [=]() -> QScriptValueList {
return QScriptValueList() << entityItemID.toScriptValue(this);
});
using SingleEntityHandler = std::function<void(const EntityItemID&)>;
auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler {
return [this, eventName](const EntityItemID& entityItemID) {
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 {
generalHandler(entityItemID, eventName, [=]() -> QScriptValueList {
return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this);
});
using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>;
auto makeMouseHandler = [this](QString eventName) -> MouseHandler {
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::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::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity"));
connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this,
[=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) {
generalHandler(idA, "collisionWithEntity", [=]() {
return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision);
});
});
connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity"));
}
if (!_registeredHandlers.contains(entityID)) {
_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) {
return QScriptValue(); // bail early
}
@ -567,27 +596,30 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN
QScriptValue result;
#ifdef THREAD_DEBUGGING
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
QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, program),
Q_ARG(const QString&, sourceCode),
Q_ARG(const QString&, fileName),
Q_ARG(int, lineNumber));
return result;
}
// Check syntax
const QScriptProgram program(sourceCode, fileName, lineNumber);
if (!hasCorrectSyntax(program)) {
return QScriptValue();
}
_evaluatesPending++;
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
if (hasUncaughtException()) {
int line = uncaughtExceptionLineNumber();
qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString();
}
_evaluatesPending--;
++_evaluatesPending;
const auto result = QScriptEngine::evaluate(program);
--_evaluatesPending;
const auto hadUncaughtException = hadUncauchtExceptions(*this, program.fileName());
if (_wantSignals) {
emit evaluationFinished(result, hasUncaughtException());
emit evaluationFinished(result, hadUncaughtException);
}
clearExceptions();
return result;
}
@ -605,7 +637,7 @@ void ScriptEngine::run() {
emit runningStateChanged();
}
QScriptValue result = evaluate(_scriptContents);
QScriptValue result = evaluate(_scriptContents, _fileNameString);
QElapsedTimer startTime;
startTime.start();
@ -646,15 +678,6 @@ void ScriptEngine::run() {
qint64 now = usecTimestampNow();
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 (_wantSignals) {
emit update(deltaTime);
@ -662,6 +685,9 @@ void ScriptEngine::run() {
}
lastUpdate = now;
if (hadUncauchtExceptions(*this, _fileNameString)) {
stop();
}
}
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
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()) {
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);
return;
}
@ -915,9 +941,8 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e
}
QScriptValueList handlersForEvent = handlersOnEntity[eventName];
if (!handlersForEvent.isEmpty()) {
QScriptValueList args = argGenerator();
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>();
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
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents);
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID;
qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":"
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL;
QScriptProgram program(contents, fileName);
if (!hasCorrectSyntax(program)) {
if (!isFileUrl) {
scriptCache->addScriptToBadScriptList(scriptOrURL);
}
@ -1000,12 +1021,15 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
}
QScriptEngine sandbox;
QScriptValue testConstructor = sandbox.evaluate(contents);
QScriptValue testConstructor = sandbox.evaluate(program);
if (hadUncauchtExceptions(sandbox, program.fileName())) {
return;
}
if (!testConstructor.isFunction()) {
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID;
qCDebug(scriptengine) << " NOT CONSTRUCTOR";
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL;
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n"
" NOT CONSTRUCTOR\n"
" SCRIPT:" << scriptOrURL;
if (!isFileUrl) {
scriptCache->addScriptToBadScriptList(scriptOrURL);
}
@ -1018,7 +1042,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
}
QScriptValue entityScriptConstructor = evaluate(contents);
QScriptValue entityScriptConstructor = evaluate(contents, fileName);
QScriptValue entityScriptObject = entityScriptConstructor.construct();
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
_entityScripts[entityID] = newDetails;

View file

@ -88,7 +88,7 @@ public:
Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value);
/// 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
/// 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);
void stopTimer(QTimer* timer);
AbstractControllerScriptingInterface* _controllerScriptingInterface;
QString _fileNameString;
Quat _quatLibrary;
@ -193,7 +193,7 @@ private:
ArrayBufferClass* _arrayBufferClass;
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);
static QSet<ScriptEngine*> _allKnownScriptEngines;

View file

@ -135,7 +135,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
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));
return logMessage;
}