working to clean up the QtScript implementation and move towards completion of the proxy interface

This commit is contained in:
Heather Anderson 2021-08-29 21:01:40 -07:00 committed by ksuprynowicz
parent 1e018dbc64
commit 2dd9d784a9
38 changed files with 2239 additions and 922 deletions

View file

@ -1,10 +0,0 @@
set(TARGET_NAME script-engine-qtscript)
# FIXME Move undo scripting interface to application and remove Widgets
setup_hifi_library(Gui Script ScriptTools)
link_hifi_libraries()
include_hifi_library_headers(animation)
include_hifi_library_headers(entities)
include_hifi_library_headers(networking)
include_hifi_library_headers(shared)
include_hifi_library_headers(script-engine)

View file

@ -1,332 +0,0 @@
//
// BaseScriptEngine.cpp
// libraries/script-engine/src
//
// Created by Timothy Dedischew on 02/01/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "BaseScriptEngine.h"
#include "SharedLogging.h"
#include <QtCore/QString>
#include <QtCore/QThread>
#include <QtCore/QUrl>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptValueIterator>
#include <QtScript/QScriptContextInfo>
#include "Profile.h"
bool BaseScriptEngine::IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method) {
if (QThread::currentThread() == thread) {
return true;
}
qCCritical(shared) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3")
.arg(method).arg(thread ? thread->objectName() : "(!thread)").arg(QThread::currentThread()->objectName());
qCDebug(shared) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)";
Q_ASSERT(false);
return false;
}
// engine-aware JS Error copier and factory
QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QString& type) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return unboundNullValue();
}
auto other = _other;
if (other.isString()) {
other = newObject();
other.setProperty("message", _other.toString());
}
auto proto = globalObject().property(type);
if (!proto.isFunction()) {
proto = globalObject().property(other.prototype().property("constructor").property("name").toString());
}
if (!proto.isFunction()) {
#ifdef DEBUG_JS_EXCEPTIONS
qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead";
#endif
proto = globalObject().property("Error");
}
if (other.engine() != this) {
// JS Objects are parented to a specific script engine instance
// -- this effectively ~clones it locally by routing through a QVariant and back
other = toScriptValue(other.toVariant());
}
// ~ var err = new Error(other.message)
auto err = proto.construct(QScriptValueList({other.property("message")}));
// transfer over any existing properties
QScriptValueIterator it(other);
while (it.hasNext()) {
it.next();
err.setProperty(it.name(), it.value());
}
return err;
}
// check syntax and when there are issues returns an actual "SyntaxError" with the details
QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return unboundNullValue();
}
const auto syntaxCheck = checkSyntax(sourceCode);
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
auto err = globalObject().property("SyntaxError")
.construct(QScriptValueList({syntaxCheck.errorMessage()}));
err.setProperty("fileName", fileName);
err.setProperty("lineNumber", syntaxCheck.errorLineNumber());
err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber());
err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP));
{
const auto error = syntaxCheck.errorMessage();
const auto line = QString::number(syntaxCheck.errorLineNumber());
const auto column = QString::number(syntaxCheck.errorColumnNumber());
// for compatibility with legacy reporting
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column);
err.setProperty("formatted", message);
}
return err;
}
return QScriptValue();
}
// this pulls from the best available information to create a detailed snapshot of the current exception
QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return unboundNullValue();
}
if (!hasUncaughtException()) {
return unboundNullValue();
}
auto exception = uncaughtException();
// ensure the error object is engine-local
auto err = makeError(exception);
// not sure why Qt does't offer uncaughtExceptionFileName -- but the line number
// on its own is often useless/wrong if arbitrarily married to a filename.
// when the error object already has this info, it seems to be the most reliable
auto fileName = exception.property("fileName").toString();
auto lineNumber = exception.property("lineNumber").toInt32();
// the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace
auto backtrace = uncaughtExceptionBacktrace();
if (backtrace.isEmpty()) {
// fallback to the error object
backtrace = exception.property("stack").toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP);
}
// the ad hoc "detail" property can be used now to embed additional clues
auto detail = exception.property("detail").toString();
if (detail.isEmpty()) {
detail = extraDetail;
} else if (!extraDetail.isEmpty()) {
detail += "(" + extraDetail + ")";
}
if (lineNumber <= 0) {
lineNumber = uncaughtExceptionLineNumber();
}
if (fileName.isEmpty()) {
// climb the stack frames looking for something useful to display
for (auto c = currentContext(); c && fileName.isEmpty(); c = c->parentContext()) {
QScriptContextInfo info { c };
if (!info.fileName().isEmpty()) {
// take fileName:lineNumber as a pair
fileName = info.fileName();
lineNumber = info.lineNumber();
if (backtrace.isEmpty()) {
backtrace = c->backtrace();
}
break;
}
}
}
err.setProperty("fileName", fileName);
err.setProperty("lineNumber", lineNumber );
err.setProperty("detail", detail);
err.setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP));
#ifdef DEBUG_JS_EXCEPTIONS
err.setProperty("_fileName", exception.property("fileName").toString());
err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP));
err.setProperty("_lineNumber", uncaughtExceptionLineNumber());
#endif
return err;
}
bool BaseScriptEngine::raiseException(const QScriptValue& exception) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return false;
}
if (currentContext()) {
// we have an active context / JS stack frame so throw the exception per usual
currentContext()->throwValue(makeError(exception));
return true;
} else {
// we are within a pure C++ stack frame (ie: being called directly by other C++ code)
// in this case no context information is available so just emit the exception for reporting
emit unhandledException(makeError(exception));
}
return false;
}
bool BaseScriptEngine::maybeEmitUncaughtException(const QString& debugHint) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return false;
}
if (!isEvaluating() && hasUncaughtException()) {
emit unhandledException(cloneUncaughtException(debugHint));
clearExceptions();
return true;
}
return false;
}
QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) {
PROFILE_RANGE(script, "evaluateInClosure");
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return unboundNullValue();
}
const auto fileName = program.fileName();
const auto shortName = QUrl(fileName).fileName();
QScriptValue result;
QScriptValue oldGlobal;
auto global = closure.property("global");
if (global.isObject()) {
#ifdef DEBUG_JS
qCDebug(shared) << " setting global = closure.global" << shortName;
#endif
oldGlobal = globalObject();
setGlobalObject(global);
}
auto context = pushContext();
auto thiz = closure.property("this");
if (thiz.isObject()) {
#ifdef DEBUG_JS
qCDebug(shared) << " setting this = closure.this" << shortName;
#endif
context->setThisObject(thiz);
}
context->pushScope(closure);
#ifdef DEBUG_JS
qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
#endif
{
result = BaseScriptEngine::evaluate(program);
if (hasUncaughtException()) {
auto err = cloneUncaughtException(__FUNCTION__);
#ifdef DEBUG_JS_EXCEPTIONS
qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();
err.setProperty("_result", result);
#endif
result = err;
}
}
#ifdef DEBUG_JS
qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
#endif
popContext();
if (oldGlobal.isValid()) {
#ifdef DEBUG_JS
qCDebug(shared) << " restoring global" << shortName;
#endif
setGlobalObject(oldGlobal);
}
return result;
}
// Lambda
QScriptValue BaseScriptEngine::newLambdaFunction(std::function<QScriptValue(QScriptContext *, QScriptEngine*)> operation, const QScriptValue& data, const QScriptEngine::ValueOwnership& ownership) {
auto lambda = new Lambda(this, operation, data);
auto object = newQObject(lambda, ownership);
auto call = object.property("call");
call.setPrototype(object); // context->callee().prototype() === Lambda QObject
call.setData(data); // context->callee().data() will === data param
return call;
}
QString Lambda::toString() const {
return QString("[Lambda%1]").arg(data.isValid() ? " " + data.toString() : data.toString());
}
Lambda::~Lambda() {
#ifdef DEBUG_JS_LAMBDA_FUNCS
qDebug() << "~Lambda" << "this" << this;
#endif
}
Lambda::Lambda(QScriptEngine *engine, std::function<QScriptValue(QScriptContext *, QScriptEngine*)> operation, QScriptValue data)
: engine(engine), operation(operation), data(data) {
#ifdef DEBUG_JS_LAMBDA_FUNCS
qDebug() << "Lambda" << data.toString();
#endif
}
QScriptValue Lambda::call() {
if (!BaseScriptEngine::IS_THREADSAFE_INVOCATION(engine->thread(), __FUNCTION__)) {
return BaseScriptEngine::unboundNullValue();
}
return operation(engine->currentContext(), engine);
}
QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName) {
auto engine = scopeOrCallback.engine();
if (!engine) {
return scopeOrCallback;
}
auto scope = QScriptValue();
auto callback = scopeOrCallback;
if (scopeOrCallback.isObject()) {
if (methodOrName.isString()) {
scope = scopeOrCallback;
callback = scope.property(methodOrName.toString());
} else if (methodOrName.isFunction()) {
scope = scopeOrCallback;
callback = methodOrName;
} else if (!methodOrName.isValid()) {
// instantiate from an existing scoped handler object
if (scopeOrCallback.property("callback").isFunction()) {
scope = scopeOrCallback.property("scope");
callback = scopeOrCallback.property("callback");
}
}
}
auto handler = engine->newObject();
handler.setProperty("scope", scope);
handler.setProperty("callback", callback);
return handler;
}
QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result) {
return handler.property("callback").call(handler.property("scope"), QScriptValueList({ err, result }));
}
#ifdef DEBUG_JS
void BaseScriptEngine::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) {
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
return;
}
if (!header.isEmpty()) {
qCDebug(shared) << header;
}
if (!object.isObject()) {
qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString();
return;
}
QScriptValueIterator it(object);
while (it.hasNext()) {
it.next();
qCDebug(shared) << it.name() << ":" << it.value().toString();
}
if (!footer.isEmpty()) {
qCDebug(shared) << footer;
}
}
#endif

View file

@ -1,493 +0,0 @@
//
// ScriptEngineQtScript.cpp
// libraries/script-engine-qtscript/src
//
// Created by Brad Hefta-Gaub on 12/14/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptEngineQtScript.h"
#include <chrono>
#include <thread>
#include <QtCore/QCoreApplication>
#include <QtCore/QEventLoop>
#include <QtCore/QFileInfo>
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <QtCore/QRegularExpression>
#include <QtCore/QFuture>
#include <QtConcurrent/QtConcurrentRun>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMenu>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <QtScript/QScriptContextInfo>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptValueIterator>
#include <QtScriptTools/QScriptEngineDebugger>
#include <shared/LocalFileAccessGate.h>
#include <shared/QtHelpers.h>
#include <shared/AbstractLoggerInterface.h>
//#include <AudioConstants.h>
//#include <AudioEffectOptions.h>
//#include <AvatarData.h>
//#include <DebugDraw.h>
//#include <EntityScriptingInterface.h>
//#include <MessagesClient.h>
//#include <NetworkAccessManager.h>
//#include <PathUtils.h>
//#include <ResourceScriptingInterface.h>
//#include <UserActivityLoggerScriptingInterface.h>
//#include <NodeList.h>
//#include <ScriptAvatarData.h>
//#include <udt/PacketHeaders.h>
//#include <UUID.h>
//#include <controllers/ScriptingInterface.h>
//#include <AnimationObject.h>
#include <ScriptEngineLogging.h>
//#include "ArrayBufferViewClass.h"
//#include "AssetScriptingInterface.h"
//#include "BatchLoader.h"
//#include "BaseScriptEngine.h"
//#include "DataViewClass.h"
//#include "EventTypes.h"
//#include "FileScriptingInterface.h" // unzip project
//#include "MenuItemProperties.h"
//#include "ScriptAudioInjector.h"
//#include "ScriptAvatarData.h"
//#include "ScriptCache.h"
//#include "TypedArrays.h"
//#include "XMLHttpRequestClass.h"
//#include "WebSocketClass.h"
//#include "RecordingScriptingInterface.h"
//#include "ScriptEngines.h"
//#include "StackTestScriptingInterface.h"
//#include "ModelScriptingInterface.h"
//#include <Profile.h>
//#include "../../midi/src/Midi.h" // FIXME why won't a simpler include work?
//#include "MIDIEvent.h"
//#include "SettingHandle.h"
//#include <AddressManager.h>
//#include <NetworkingConstants.h>
//#include <ThreadHelpers.h>
static const int MAX_MODULE_ID_LENGTH { 4096 };
static const int MAX_DEBUG_VALUE_LENGTH { 80 };
static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS =
QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects;
static const QScriptValue::PropertyFlags READONLY_PROP_FLAGS { QScriptValue::ReadOnly | QScriptValue::Undeletable };
static const QScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS { READONLY_PROP_FLAGS | QScriptValue::SkipInEnumeration };
static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true };
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
int scriptEnginePointerMetaID = qRegisterMetaType<ScriptEngineQtScriptPointer>();
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
// assemble the message by concatenating our arguments
QString message = "";
for (int i = 0; i < context->argumentCount(); i++) {
if (i > 0) {
message += " ";
}
message += context->argument(i).toString();
}
// was this generated by a script engine? If we don't recognize it then send the message and exit
ScriptEngineQtScript* scriptEngine = qobject_cast<ScriptEngineQtScript*>(engine);
if (!scriptEngine) {
qCDebug(scriptengine_script, "%s", qUtf8Printable(message));
return QScriptValue();
}
QString filename;
auto scriptManager = scriptEngine->manager();
if (scriptManager) {
filename = scriptManager->getFilename();
}
// This message was sent by one of our script engines, let's try to see if we can find the source.
// Note that the first entry in the backtrace should be "print" and is somewhat useless to us
AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get();
if (loggerInterface && loggerInterface->showSourceDebugging()) {
QScriptContext* userContext = context;
while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) {
userContext = userContext->parentContext();
}
QString location;
if (userContext) {
QScriptContextInfo contextInfo(userContext);
QString fileName = contextInfo.fileName();
int lineNumber = contextInfo.lineNumber();
QString functionName = contextInfo.functionName();
location = functionName;
if (!fileName.isEmpty()) {
if (location.isEmpty()) {
location = fileName;
} else {
location = QString("%1 at %2").arg(location).arg(fileName);
}
}
if (lineNumber != -1) {
location = QString("%1:%2").arg(location).arg(lineNumber);
}
}
if (location.isEmpty()) {
location = filename;
}
// give the script engine a chance to notify the system about this message
scriptEngine->print(message);
// send the message to debug log
qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(location), qUtf8Printable(message));
} else {
scriptEngine->print(message);
// prefix the script engine name to help disambiguate messages in the main debug log
qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(filename), qUtf8Printable(message));
}
return QScriptValue();
}
ScriptEngineQtScript::ScriptEngineQtScript(ScriptManager* scriptManager) :
BaseScriptEngine(),
_manager(scriptManager),
_arrayBufferClass(new ArrayBufferClass(this))
{
if (_manager) {
connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) {
if (Base::hasUncaughtException()) {
// the engine's uncaughtException() seems to produce much better stack traces here
emit _manager->unhandledException(Base::cloneUncaughtException("signalHandlerException"));
Base::clearExceptions();
} else {
// ... but may not always be available -- so if needed we fallback to the passed exception
emit _manager->unhandledException(exception);
}
}, Qt::DirectConnection);
}
setProcessEventsInterval(MSECS_PER_SECOND);
}
bool ScriptEngineQtScript::isDebugMode() const {
#if defined(DEBUG)
return true;
#else
return false;
#endif
}
ScriptEngineQtScript::~ScriptEngineQtScript() {}
void ScriptEngineQtScript::disconnectNonEssentialSignals() {
disconnect();
QThread* workerThread;
// Ensure the thread should be running, and does exist
if (_isRunning && _isThreaded && (workerThread = Base::thread())) {
connect(this, &QObject::destroyed, workerThread, &QThread::quit);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
}
}
void ScriptEngineQtScript::executeOnScriptThread(std::function<void()> function, const Qt::ConnectionType& type ) {
if (QThread::currentThread() != Base::thread()) {
QMetaObject::invokeMethod(this, "executeOnScriptThread", type, Q_ARG(std::function<void()>, function));
return;
}
function();
}
void ScriptEngineQtScript::registerValue(const QString& valueName, QScriptValue value) {
if (QThread::currentThread() != Base::thread()) {
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
#endif
QMetaObject::invokeMethod(this, "registerValue",
Q_ARG(const QString&, valueName),
Q_ARG(QScriptValue, value));
return;
}
QStringList pathToValue = valueName.split(".");
int partsToGo = pathToValue.length();
QScriptValue partObject = globalObject();
for (const auto& pathPart : pathToValue) {
partsToGo--;
if (!partObject.property(pathPart).isValid()) {
if (partsToGo > 0) {
//QObject *object = new QObject;
QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership);
partObject.setProperty(pathPart, partValue);
} else {
partObject.setProperty(pathPart, value);
}
}
partObject = partObject.property(pathPart);
}
}
void ScriptEngineQtScript::registerGlobalObject(const QString& name, QObject* object) {
if (QThread::currentThread() != Base::thread()) {
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
#endif
QMetaObject::invokeMethod(this, "registerGlobalObject",
Q_ARG(const QString&, name),
Q_ARG(QObject*, object));
return;
}
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptEngineQtScript::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name;
#endif
if (!Base::globalObject().property(name).isValid()) {
if (object) {
QScriptValue value = Base::newQObject(object, QScriptEngine::QtOwnership, DEFAULT_QOBJECT_WRAP_OPTIONS);
Base::globalObject().setProperty(name, value);
} else {
Base::globalObject().setProperty(name, QScriptValue());
}
}
}
void ScriptEngineQtScript::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != Base::thread()) {
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
#endif
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name;
#endif
QScriptValue scriptFun = Base::newFunction(functionSignature, numArguments);
Base::globalObject().setProperty(name, scriptFun);
}
void ScriptEngineQtScript::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != Base::thread()) {
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name;
#endif
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name;
#endif
QScriptValue object = Base::globalObject().property(parent);
if (object.isValid()) {
QScriptValue scriptFun = Base::newFunction(functionSignature, numArguments);
object.setProperty(name, scriptFun);
}
}
void ScriptEngineQtScript::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, const QString& parent) {
if (QThread::currentThread() != Base::thread()) {
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
" name:" << name << "parent:" << parent;
#endif
QMetaObject::invokeMethod(this, "registerGetterSetter",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, getter),
Q_ARG(QScriptEngine::FunctionSignature, setter),
Q_ARG(const QString&, parent));
return;
}
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptEngineQtScript::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent;
#endif
QScriptValue setterFunction = Base::newFunction(setter, 1);
QScriptValue getterFunction = Base::newFunction(getter);
if (!parent.isNull() && !parent.isEmpty()) {
QScriptValue object = Base::globalObject().property(parent);
if (object.isValid()) {
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
}
} else {
Base::globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
Base::globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);
}
}
// this is not redundant -- the version in BaseScriptEngine is specifically not Q_INVOKABLE
QScriptValue ScriptEngineQtScript::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) {
return BaseScriptEngine::evaluateInClosure(closure, program);
}
QScriptValue ScriptEngineQtScript::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
return QScriptValue(); // bail early
}
if (QThread::currentThread() != Base::thread()) {
QScriptValue result;
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber;
#endif
BLOCKING_INVOKE_METHOD(this, "evaluate",
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, sourceCode),
Q_ARG(const QString&, fileName),
Q_ARG(int, lineNumber));
return result;
}
// Check syntax
auto syntaxError = Base::lintScript(sourceCode, fileName);
if (syntaxError.isError()) {
if (!Base::isEvaluating()) {
syntaxError.setProperty("detail", "evaluate");
}
Base::raiseException(syntaxError);
Base::maybeEmitUncaughtException("lint");
return syntaxError;
}
QScriptProgram program { sourceCode, fileName, lineNumber };
if (program.isNull()) {
// can this happen?
auto err = Base::makeError("could not create QScriptProgram for " + fileName);
Base::raiseException(err);
Base::maybeEmitUncaughtException("compile");
return err;
}
QScriptValue result;
{
result = BaseScriptEngine::evaluate(program);
Base::maybeEmitUncaughtException("evaluate");
}
return result;
}
void ScriptEngineQtScript::updateMemoryCost(const qint64& deltaSize) {
if (deltaSize > 0) {
// We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only.
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
reportAdditionalMemoryCost(deltaSize);
#endif
}
}
void ScriptEngineQtScript::print(const QString& message) {
QString filename;
auto scriptManager = manager();
if (scriptManager) {
filename = scriptManager->getFilename();
}
emit printedMessage(message, filename);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ScriptEngine implementation
/*
ScriptValuePointer ScriptEngineQtScript::globalObject() const {
return Base::globalObject();
}
ScriptManager* ScriptEngineQtScript::manager() const {
}
ScriptValuePointer ScriptEngineQtScript::newArray(uint length) {
return Base::newArray(length);
}
ScriptValuePointer ScriptEngineQtScript::newArrayBuffer(const QByteArray& message) {
QScriptValue data = Base::newVariant(QVariant::fromValue(message));
QScriptValue ctor = Base::globalObject().property("ArrayBuffer");
auto array = qscriptvalue_cast<ArrayBufferClass*>(ctor.data());
if (!array) {
return undefinedValue()
}
return Base::newObject(array, data);
}
ScriptValuePointer ScriptEngineQtScript::newObject() {
return Base::newObject();
}
ScriptProgramPointer ScriptEngineQtScript::newProgram(const QString& sourceCode, const QString& fileName) {
}
ScriptValuePointer ScriptEngineQtScript::newQObject(QObject* obj) {
}
ScriptValuePointer ScriptEngineQtScript::newValue(bool value) {
}
ScriptValuePointer ScriptEngineQtScript::newValue(int value) {
}
ScriptValuePointer ScriptEngineQtScript::newValue(uint value) {
}
ScriptValuePointer ScriptEngineQtScript::newValue(double value) {
}
ScriptValuePointer ScriptEngineQtScript::newValue(const QString& value) {
}
ScriptValuePointer ScriptEngineQtScript::newValue(const QLatin1String& value) {
}
ScriptValuePointer ScriptEngineQtScript::newValue(const char* value) {
}
ScriptValuePointer ScriptEngineQtScript::newVariant(const QVariant& value) {
}
ScriptValuePointer ScriptEngineQtScript::nullValue() {
return Base::nullValue();
}
void ScriptEngineQtScript::setDefaultPrototype(int metaTypeId, const ScriptValuePointer& prototype) {
}
ScriptValuePointer ScriptEngineQtScript::undefinedValue() {
return Base::undefinedValue();
}
*/

View file

@ -1,13 +1,13 @@
set(TARGET_NAME script-engine)
# FIXME Move undo scripting interface to application and remove Widgets
setup_hifi_library(Network WebSockets)
setup_hifi_library(Gui Network Script ScriptTools WebSockets)
target_zlib()
if (NOT ANDROID)
target_quazip()
endif ()
link_hifi_libraries(script-engine-qtscript shaders)
link_hifi_libraries(shaders)
include_hifi_library_headers(animation)
include_hifi_library_headers(audio)
include_hifi_library_headers(avatars)

View file

@ -14,11 +14,16 @@
#include <QtCore/QSharedPointer>
#include <QtCore/QString>
#include <QtCore/QStringList>
class ScriptContext;
class ScriptEngine;
class ScriptFunctionContext;
class ScriptValue;
using ScriptValuePointer = QSharedPointer<ScriptValue>;
using ScriptContextPointer = QSharedPointer<ScriptContext>;
using ScriptFunctionContextPointer = QSharedPointer<ScriptFunctionContext>;
using ScriptEnginePointer = QSharedPointer<ScriptEngine>;
using ScriptValuePointer = QSharedPointer<ScriptValue>;
class ScriptFunctionContext {
public:
@ -43,8 +48,8 @@ public:
virtual QStringList backtrace() const = 0;
virtual ScriptValuePointer callee() const = 0;
virtual ScriptEnginePointer engine() const = 0;
virtual ScriptFunctionContext* functionContext() const = 0;
virtual ScriptContext* parentContext() const = 0;
virtual ScriptFunctionContextPointer functionContext() const = 0;
virtual ScriptContextPointer parentContext() const = 0;
virtual ScriptValuePointer thisObject() const = 0;
virtual ScriptValuePointer throwError(const QString& text) = 0;
virtual ScriptValuePointer throwValue(const ScriptValuePointer& value) = 0;

View file

@ -0,0 +1,67 @@
//
// ScriptEngine.cpp
// libraries/script-engine/src
//
// Created by Brad Hefta-Gaub on 12/14/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptEngine.h"
#include "ScriptEngineLogging.h"
#include "ScriptValue.h"
#include "qtscript/ScriptEngineQtScript.h"
ScriptEnginePointer newScriptEngine(ScriptManager* manager) {
return QSharedPointer<ScriptEngineQtScript>(new ScriptEngineQtScript(manager));
}
ScriptValuePointer makeScopedHandlerObject(ScriptValuePointer scopeOrCallback, ScriptValuePointer methodOrName) {
auto engine = scopeOrCallback->engine();
if (!engine) {
return scopeOrCallback;
}
ScriptValuePointer scope;
ScriptValuePointer callback = scopeOrCallback;
if (scopeOrCallback->isObject()) {
if (methodOrName->isString()) {
scope = scopeOrCallback;
callback = scope->property(methodOrName->toString());
} else if (methodOrName->isFunction()) {
scope = scopeOrCallback;
callback = methodOrName;
} else if (!methodOrName->isValid()) {
// instantiate from an existing scoped handler object
if (scopeOrCallback->property("callback")->isFunction()) {
scope = scopeOrCallback->property("scope");
callback = scopeOrCallback->property("callback");
}
}
}
auto handler = engine->newObject();
handler->setProperty("scope", scope);
handler->setProperty("callback", callback);
return handler;
}
ScriptValuePointer callScopedHandlerObject(ScriptValuePointer handler, ScriptValuePointer err, ScriptValuePointer result) {
return handler->property("callback")->call(handler->property("scope"), ScriptValueList({ err, result }));
}
bool ScriptEngine::IS_THREADSAFE_INVOCATION(const QString& method) {
QThread* thread = this->thread();
if (QThread::currentThread() == thread) {
return true;
}
qCCritical(scriptengine) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3")
.arg(method)
.arg(thread ? thread->objectName() : "(!thread)")
.arg(QThread::currentThread()->objectName());
qCDebug(scriptengine) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)";
Q_ASSERT(false);
return false;
}

View file

@ -106,6 +106,7 @@ public:
virtual ScriptValuePointer uncaughtException() const = 0;
virtual QStringList uncaughtExceptionBacktrace() const = 0;
virtual int uncaughtExceptionLineNumber() const = 0;
virtual void updateMemoryCost(const qint64& deltaSize) = 0;
public:
// helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways

View file

@ -14,6 +14,8 @@
// Object conversion helpers (copied from QScriptEngine)
#include <QtCore/QMetaType>
#include "ScriptEngine.h"
#include "ScriptValue.h"

View file

@ -53,11 +53,9 @@
#include <controllers/ScriptingInterface.h>
#include <AnimationObject.h>
#include "ArrayBufferViewClass.h"
#include "AudioScriptingInterface.h"
#include "AssetScriptingInterface.h"
#include "BatchLoader.h"
#include "DataViewClass.h"
#include "EventTypes.h"
#include "FileScriptingInterface.h" // unzip project
#include "MenuItemProperties.h"
@ -67,7 +65,6 @@
#include "ScriptContext.h"
#include "ScriptEngineCast.h"
#include "ScriptEngineLogging.h"
#include "TypedArrays.h"
#include "XMLHttpRequestClass.h"
#include "WebSocketClass.h"
#include "RecordingScriptingInterface.h"
@ -133,8 +130,10 @@ static ScriptValuePointer debugPrint(ScriptContext* context, ScriptEngine* engin
AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get();
if (loggerInterface && loggerInterface->showSourceDebugging()) {
ScriptContext* userContext = context;
ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan
while (userContext && userContext->functionContext()->functionType() == ScriptFunctionContext::NativeFunction) {
userContext = userContext->parentContext();
parentContext = userContext->parentContext();
userContext = parentContext.data();
}
QString location;
if (userContext) {
@ -560,9 +559,10 @@ static ScriptValuePointer scriptableResourceToScriptValue(ScriptEngine* engine,
// in that case it would be too difficult to tell which one should track the memory, and
// this serves the common case (use in a single script).
auto data = resource->getResource();
if (data && !resource->isInScript()) {
auto manager = engine->manager();
if (data && manager && !resource->isInScript()) {
resource->setInScript(true);
QObject::connect(data.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64)));
QObject::connect(data.data(), SIGNAL(updateSize(qint64)), manager, SLOT(updateMemoryCost(qint64)));
}
auto object = engine->newQObject(
@ -963,6 +963,11 @@ void ScriptManager::addEventHandler(const EntityItemID& entityID, const QString&
handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
}
bool ScriptManager::isStopped() const {
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
return !scriptEngines || scriptEngines->isStopped();
}
void ScriptManager::run() {
if (QThread::currentThread() != qApp->thread() && _context == Context::CLIENT_SCRIPT) {
// Flag that we're allowed to access local HTML files on UI created from C++ calls on this thread
@ -974,8 +979,7 @@ void ScriptManager::run() {
auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown";
PROFILE_SET_THREAD_NAME("Script: " + name);
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
if (isStopped()) {
return; // bail early - avoid setting state in init(), as evaluate() will bail too
}
@ -1228,21 +1232,13 @@ void ScriptManager::callAnimationStateHandler(ScriptValuePointer callback, AnimV
}
void ScriptManager::updateMemoryCost(const qint64& deltaSize) {
if (deltaSize > 0) {
// We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only.
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
reportAdditionalMemoryCost(deltaSize);
#endif
}
_engine->updateMemoryCost(deltaSize);
}
void ScriptManager::timerFired() {
{
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename());
return; // bail early
}
if (isStopped()) {
scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename());
return; // bail early
}
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
@ -1292,8 +1288,7 @@ QObject* ScriptManager::setupTimerWithInterval(const ScriptValuePointer& functio
}
QObject* ScriptManager::setInterval(const ScriptValuePointer& function, int intervalMS) {
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
if (isStopped()) {
scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename());
return NULL; // bail early
}
@ -1302,8 +1297,7 @@ QObject* ScriptManager::setInterval(const ScriptValuePointer& function, int inte
}
QObject* ScriptManager::setTimeout(const ScriptValuePointer& function, int timeoutMS) {
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
if (isStopped()) {
scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename());
return NULL; // bail early
}
@ -1335,10 +1329,12 @@ QUrl ScriptManager::resolvePath(const QString& include) const {
// to the first absolute URL in the JS scope chain
QUrl parentURL;
auto context = _engine->currentContext();
ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan
do {
auto contextInfo = context->functionContext();
parentURL = QUrl(contextInfo->fileName());
context = context->parentContext();
parentContext = context->parentContext();
context = parentContext.data();
} while (parentURL.isRelative() && context);
if (parentURL.isRelative()) {
@ -1478,8 +1474,9 @@ ScriptValuePointer ScriptManager::currentModule() {
auto jsRequire = _engine->globalObject()->property("Script")->property("require");
auto cache = jsRequire->property("cache");
auto candidate = ScriptValuePointer();
for (auto c = _engine->currentContext(); c && !candidate->isObject(); c = c->parentContext()) {
auto contextInfo = c->functionContext();
ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan
for (auto context = _engine->currentContext(); context && !candidate->isObject(); parentContext = context->parentContext(), context = parentContext.data()) {
auto contextInfo = context->functionContext();
candidate = cache->property(contextInfo->fileName());
}
if (!candidate->isObject()) {
@ -1737,8 +1734,7 @@ void ScriptManager::include(const QStringList& includeFiles, ScriptValuePointer
if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) {
return;
}
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
if (isStopped()) {
scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:"
+ includeFiles.join(",") + "parent script:" + getFilename());
return; // bail early
@ -1832,8 +1828,7 @@ void ScriptManager::include(const QStringList& includeFiles, ScriptValuePointer
}
void ScriptManager::include(const QString& includeFile, ScriptValuePointer callback) {
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
if (isStopped()) {
scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:"
+ includeFile + "parent script:" + getFilename());
return; // bail early
@ -1851,8 +1846,7 @@ void ScriptManager::load(const QString& loadFile) {
if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) {
return;
}
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
if (!scriptEngines || scriptEngines->isStopped()) {
if (isStopped()) {
scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:"
+ loadFile + "parent script:" + getFilename());
return; // bail early
@ -2689,3 +2683,7 @@ QString ScriptManager::formatException(const ScriptValuePointer& exception, bool
}
return result;
}
ScriptValuePointer ScriptManager::evaluate(const QString& program, const QString& fileName) {
return _engine->evaluate(program, fileName);
}

View file

@ -177,10 +177,12 @@ public:
QString getFilename() const;
ScriptEnginePointer engine();
inline ScriptEnginePointer engine() { return _engine; }
QList<EntityItemID> getListOfEntityScriptIDs();
bool isStopped() const;
/**jsdoc
* Stops and unloads the current script.
* <p><strong>Warning:</strong> If an assignment client script, the script gets restarted after stopping.</p>

View file

@ -52,6 +52,7 @@ public:
virtual ScriptValuePointer call(const ScriptValuePointer& thisObject, const ScriptValuePointer& arguments) = 0;
virtual ScriptValuePointer construct(const ScriptValueList& args = ScriptValueList()) = 0;
virtual ScriptValuePointer construct(const ScriptValuePointer& arguments) = 0;
virtual ScriptValuePointer data() const = 0;
virtual ScriptEnginePointer engine() const = 0;
inline bool equals(const ScriptValuePointer& other) const;
inline bool isArray() const;
@ -68,6 +69,7 @@ public:
virtual ScriptValueIteratorPointer newIterator() = 0;
virtual ScriptValuePointer property(const QString& name, const ResolveFlags& mode = ResolvePrototype) const = 0;
virtual ScriptValuePointer property(quint32 arrayIndex, const ResolveFlags& mode = ResolvePrototype) const = 0;
virtual void setData(const ScriptValuePointer& val) = 0;
virtual void setProperty(const QString& name,
const ScriptValuePointer& value,
const PropertyFlags& flags = KeepExistingFlags) = 0;

View file

@ -0,0 +1,24 @@
//
// Scriptable.cpp
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/22/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Scriptable.h"
#include <QtCore/QThreadStorage>
static QThreadStorage<ScriptContext*> ScriptContextStore;
ScriptContext* Scriptable::context() {
return ScriptContextStore.localData();
}
void Scriptable::setContext(ScriptContext* context) {
ScriptContextStore.setLocalData(context);
}

View file

@ -0,0 +1,180 @@
//
// ArrayBufferClass.cpp
//
//
// Created by Clement on 7/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ArrayBufferClass.h"
#include <QDebug>
#include "ArrayBufferPrototype.h"
#include "DataViewClass.h"
#include "ScriptEngineQtScript.h"
#include "TypedArrays.h"
static const QString CLASS_NAME = "ArrayBuffer";
// FIXME: Q_DECLARE_METATYPE is global and really belongs in a shared header file, not per .cpp like this
// (see DataViewClass.cpp, etc. which would also have to be updated to resolve)
Q_DECLARE_METATYPE(QScriptClass*)
Q_DECLARE_METATYPE(QByteArray*)
int qScriptClassPointerMetaTypeId = qRegisterMetaType<QScriptClass*>();
int qByteArrayPointerMetaTypeId = qRegisterMetaType<QByteArray*>();
ArrayBufferClass::ArrayBufferClass(ScriptEngineQtScript* scriptEngine) :
QObject(scriptEngine),
QScriptClass(scriptEngine) {
qScriptRegisterMetaType<QByteArray>(engine(), toScriptValue, fromScriptValue);
QScriptValue global = engine()->globalObject();
// Save string handles for quick lookup
_name = engine()->toStringHandle(CLASS_NAME.toLatin1());
_byteLength = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1());
// build prototype
_proto = engine()->newQObject(new ArrayBufferPrototype(this),
QScriptEngine::QtOwnership,
QScriptEngine::SkipMethodsInEnumeration |
QScriptEngine::ExcludeSuperClassMethods |
QScriptEngine::ExcludeSuperClassProperties);
_proto.setPrototype(global.property("Object").property("prototype"));
// Register constructor
_ctor = engine()->newFunction(construct, _proto);
_ctor.setData(engine()->toScriptValue(this));
engine()->globalObject().setProperty(name(), _ctor);
// Registering other array types
// The script engine is there parent so it'll delete them with itself
new DataViewClass(scriptEngine);
new Int8ArrayClass(scriptEngine);
new Uint8ArrayClass(scriptEngine);
new Uint8ClampedArrayClass(scriptEngine);
new Int16ArrayClass(scriptEngine);
new Uint16ArrayClass(scriptEngine);
new Int32ArrayClass(scriptEngine);
new Uint32ArrayClass(scriptEngine);
new Float32ArrayClass(scriptEngine);
new Float64ArrayClass(scriptEngine);
}
QScriptValue ArrayBufferClass::newInstance(qint32 size) {
const qint32 MAX_LENGTH = 100000000;
if (size < 0) {
engine()->evaluate("throw \"ArgumentError: negative length\"");
return QScriptValue();
}
if (size > MAX_LENGTH) {
engine()->evaluate("throw \"ArgumentError: absurd length\"");
return QScriptValue();
}
// We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only.
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
engine()->reportAdditionalMemoryCost(size);
#endif
QScriptEngine* eng = engine();
QVariant variant = QVariant::fromValue(QByteArray(size, 0));
QScriptValue data = eng->newVariant(variant);
return engine()->newObject(this, data);
}
QScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) {
QScriptValue data = engine()->newVariant(QVariant::fromValue(ba));
return engine()->newObject(this, data);
}
QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* engine) {
ArrayBufferClass* cls = qscriptvalue_cast<ArrayBufferClass*>(context->callee().data());
if (!cls) {
// return if callee (function called) is not of type ArrayBuffer
return QScriptValue();
}
QScriptValue arg = context->argument(0);
if (!arg.isValid() || !arg.isNumber()) {
return QScriptValue();
}
quint32 size = arg.toInt32();
QScriptValue newObject = cls->newInstance(size);
if (context->isCalledAsConstructor()) {
// if called with keyword new, replace this object.
context->setThisObject(newObject);
return engine->undefinedValue();
}
return newObject;
}
QScriptClass::QueryFlags ArrayBufferClass::queryProperty(const QScriptValue& object,
const QScriptString& name,
QueryFlags flags, uint* id) {
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data());
if (ba && name == _byteLength) {
// if the property queried is byteLength, only handle read access
return flags &= HandlesReadAccess;
}
return 0; // No access
}
QScriptValue ArrayBufferClass::property(const QScriptValue& object,
const QScriptString& name, uint id) {
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data());
if (ba && name == _byteLength) {
return ba->length();
}
return QScriptValue();
}
QScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const QScriptValue& object,
const QScriptString& name, uint id) {
return QScriptValue::Undeletable;
}
QString ArrayBufferClass::name() const {
return _name.toString();
}
QScriptValue ArrayBufferClass::prototype() const {
return _proto;
}
QScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) {
QScriptValue ctor = engine->globalObject().property(CLASS_NAME);
ArrayBufferClass* cls = qscriptvalue_cast<ArrayBufferClass*>(ctor.data());
if (!cls) {
if (engine->currentContext()) {
engine->currentContext()->throwError("arrayBufferClass::toScriptValue -- could not get " + CLASS_NAME + " class constructor");
}
return QScriptValue::NullValue;
}
return cls->newInstance(ba);
}
void ArrayBufferClass::fromScriptValue(const QScriptValue& object, QByteArray& byteArray) {
if (object.isString()) {
// UTF-8 encoded String
byteArray = object.toString().toUtf8();
} else if (object.isArray()) {
// Array of uint8s eg: [ 128, 3, 25, 234 ]
auto Uint8Array = object.engine()->globalObject().property("Uint8Array");
auto typedArray = Uint8Array.construct(QScriptValueList{object});
if (QByteArray* buffer = qscriptvalue_cast<QByteArray*>(typedArray.property("buffer"))) {
byteArray = *buffer;
}
} else if (object.isObject()) {
// ArrayBuffer instance (or any JS class that supports coercion into QByteArray*)
if (QByteArray* buffer = qscriptvalue_cast<QByteArray*>(object.data())) {
byteArray = *buffer;
}
}
}

View file

@ -15,8 +15,7 @@
#include <QtCore/QBuffer>
#include <QtGui/QImage>
#include "ArrayBufferClass.h"
#include <QtScript/QScriptEngine>
static const int QCOMPRESS_HEADER_POSITION = 0;
static const int QCOMPRESS_HEADER_SIZE = 4;

View file

@ -13,12 +13,14 @@
#include <QDebug>
#include <QtCore/QDataStream>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
#include <glm/glm.hpp>
#include <SharedUtil.h>
#include "DataViewClass.h"
#include "ArrayBufferViewClass.h"
Q_DECLARE_METATYPE(QByteArray*)

View file

@ -0,0 +1,73 @@
//
// ScriptContextQtAgent.cpp
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/22/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptContextQtAgent.h"
#include <QtScript/QScriptEngine>
#include "../Scriptable.h"
#include "ScriptContextQtWrapper.h"
#include "ScriptEngineQtScript.h"
void ScriptContextQtAgent::contextPop() {
if (_prevAgent) {
_prevAgent->contextPop();
}
if (_engine->currentContext() == _currContext) {
_currContext.reset();
if (!_contextActive && !_contextStack.empty()) {
_currContext = _contextStack.back();
_contextStack.pop_back();
_contextActive = true;
}
}
}
void ScriptContextQtAgent::contextPush() {
if (_prevAgent) {
_prevAgent->contextPush();
}
if (_contextActive && _currContext) {
_contextStack.push_back(_currContext);
_contextActive = false;
}
_currContext.reset();
}
void ScriptContextQtAgent::functionEntry(qint64 scriptId) {
if (_prevAgent) {
_prevAgent->functionEntry(scriptId);
}
if (scriptId != -1) {
return;
}
if (!_currContext) {
_currContext = ScriptContextQtPointer(new ScriptContextQtWrapper(_engine, static_cast<QScriptEngine*>(_engine)->currentContext()));
}
Scriptable::setContext(_currContext.get());
_contextActive = true;
}
void ScriptContextQtAgent::functionExit(qint64 scriptId, const QScriptValue& returnValue) {
if (_prevAgent) {
_prevAgent->functionExit(scriptId, returnValue);
}
if (scriptId != -1) {
return;
}
_contextActive = false;
if (!_contextActive && !_contextStack.empty()) {
_currContext = _contextStack.back();
_contextStack.pop_back();
Scriptable::setContext(_currContext.get());
_contextActive = true;
}
}

View file

@ -0,0 +1,47 @@
//
// ScriptContextQtAgent.h
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/22/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ScriptContextQtAgent_h
#define hifi_ScriptContextQtAgent_h
#include <QtCore/QList>
#include <QtCore/QSharedPointer>
#include <QtScript/QScriptEngineAgent>
#include "ScriptEngineQtScript.h"
class QScriptContext;
class QScriptEngine;
class QScriptValue;
class ScriptContextQtWrapper;
using ScriptContextQtPointer = QSharedPointer<ScriptContextQtWrapper>;
class ScriptContextQtAgent : public QScriptEngineAgent {
public: // construction
inline ScriptContextQtAgent(ScriptEngineQtScript* engine, QScriptEngineAgent* prevAgent) :
QScriptEngineAgent(engine), _engine(engine), _prevAgent(prevAgent) {}
virtual ~ScriptContextQtAgent() {}
public: // QScriptEngineAgent implementation
virtual void contextPop();
virtual void contextPush();
virtual void functionEntry(qint64 scriptId);
virtual void functionExit(qint64 scriptId, const QScriptValue& returnValue);
private: // storage
bool _contextActive = false;
QList<ScriptContextQtPointer> _contextStack;
ScriptContextQtPointer _currContext;
ScriptEngineQtScript* _engine;
QScriptEngineAgent* _prevAgent;
};
#endif // hifi_ScriptContextQtAgent_h

View file

@ -0,0 +1,92 @@
//
// ScriptContextQtWrapper.cpp
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/22/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptContextQtWrapper.h"
#include <QtScript/QScriptContext>
#include "ScriptEngineQtScript.h"
#include "ScriptValueQtWrapper.h"
ScriptContextQtWrapper* ScriptContextQtWrapper::unwrap(ScriptContext* val) {
if (!val) {
return nullptr;
}
return dynamic_cast<ScriptContextQtWrapper*>(val);
}
int ScriptContextQtWrapper::argumentCount() const {
return _context->argumentCount();
}
ScriptValuePointer ScriptContextQtWrapper::argument(int index) const {
QScriptValue result = _context->argument(index);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
QStringList ScriptContextQtWrapper::backtrace() const {
return _context->backtrace();
}
ScriptValuePointer ScriptContextQtWrapper::callee() const {
QScriptValue result = _context->callee();
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptEnginePointer ScriptContextQtWrapper::engine() const {
return _engine->sharedFromThis();
}
ScriptFunctionContextPointer ScriptContextQtWrapper::functionContext() const {
return ScriptFunctionContextPointer(new ScriptFunctionContextQtWrapper(_context));
}
ScriptContextPointer ScriptContextQtWrapper::parentContext() const {
QScriptContext* result = _context->parentContext();
return ScriptContextPointer(new ScriptContextQtWrapper(_engine, result));
}
ScriptValuePointer ScriptContextQtWrapper::thisObject() const {
QScriptValue result = _context->thisObject();
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptValuePointer ScriptContextQtWrapper::throwError(const QString& text) {
QScriptValue result = _context->throwError(text);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptValuePointer ScriptContextQtWrapper::throwValue(const ScriptValuePointer& value) {
ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(value);
if (!unwrapped) {
return _engine->undefinedValue();
}
QScriptValue result = _context->throwValue(unwrapped->toQtValue());
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
QString ScriptFunctionContextQtWrapper::fileName() const {
return _value.fileName();
}
QString ScriptFunctionContextQtWrapper::functionName() const {
return _value.functionName();
}
ScriptFunctionContext::FunctionType ScriptFunctionContextQtWrapper::functionType() const {
return static_cast<ScriptFunctionContext::FunctionType>(_value.functionType());
}
int ScriptFunctionContextQtWrapper::lineNumber() const {
return _value.lineNumber();
}

View file

@ -0,0 +1,63 @@
//
// ScriptContextQtWrapper.h
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/22/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ScriptContextQtWrapper_h
#define hifi_ScriptContextQtWrapper_h
#include <QtCore/QSharedPointer>
#include <QtCore/QString>
#include <QtScript/QScriptContextInfo>
#include "../ScriptContext.h"
class QScriptContext;
class ScriptEngineQtScript;
class ScriptValue;
using ScriptValuePointer = QSharedPointer<ScriptValue>;
class ScriptContextQtWrapper : public ScriptContext {
public: // construction
inline ScriptContextQtWrapper(ScriptEngineQtScript* engine, QScriptContext* context) : _engine(engine), _context(context) {}
static ScriptContextQtWrapper* unwrap(ScriptContext* val);
inline QScriptContext* toQtValue() const { return _context; }
public: // ScriptContext implementation
virtual int argumentCount() const;
virtual ScriptValuePointer argument(int index) const;
virtual QStringList backtrace() const;
virtual ScriptValuePointer callee() const;
virtual ScriptEnginePointer engine() const;
virtual ScriptFunctionContextPointer functionContext() const;
virtual ScriptContextPointer parentContext() const;
virtual ScriptValuePointer thisObject() const;
virtual ScriptValuePointer throwError(const QString& text);
virtual ScriptValuePointer throwValue(const ScriptValuePointer& value);
private: // storage
QScriptContext* _context;
ScriptEngineQtScript* _engine;
};
class ScriptFunctionContextQtWrapper : public ScriptFunctionContext {
public: // construction
inline ScriptFunctionContextQtWrapper(QScriptContext* context) : _value(context) {}
public: // ScriptFunctionContext implementation
virtual QString fileName() const;
virtual QString functionName() const;
virtual FunctionType functionType() const;
virtual int lineNumber() const;
private: // storage
QScriptContextInfo _value;
};
#endif // hifi_ScriptContextQtWrapper_h

File diff suppressed because it is too large Load diff

View file

@ -13,50 +13,27 @@
#ifndef hifi_ScriptEngineQtScript_h
#define hifi_ScriptEngineQtScript_h
//#include <unordered_map>
//#include <vector>
//#include <QtCore/QUrl>
//#include <QtCore/QSet>
//#include <QtCore/QWaitCondition>
//#include <QtCore/QStringList>
//#include <QMap>
#include <QtCore/QByteArray>
#include <QtCore/QEnableSharedFromThis>
#include <QtCore/QMetaEnum>
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtScript/QScriptEngine>
#include <QtScriptTools/QScriptEngineDebugger>
#include <QtCore/QSharedPointer>
#include <QtCore/QString>
//#include <AnimationCache.h>
//#include <AnimVariant.h>
//#include <AvatarData.h>
//#include <AvatarHashMap.h>
//#include <LimitedNodeList.h>
//#include <EntityItemID.h>
//#include <EntityScriptUtils.h>
#include <ScriptEngine.h>
#include <ScriptManager.h>
#include <QtScript/QScriptEngine>
#include <QtScriptTools/QScriptEngineDebugger>
#include "../ScriptEngine.h"
#include "../ScriptManager.h"
//#include "PointerEvent.h"
#include "ArrayBufferClass.h"
//#include "AssetScriptingInterface.h"
//#include "AudioScriptingInterface.h"
#include "BaseScriptEngine.h"
//#include "ExternalResource.h"
//#include "Quat.h"
//#include "Mat4.h"
//#include "ScriptCache.h"
//#include "ScriptUUID.h"
//#include "Vec3.h"
//#include "ConsoleScriptingInterface.h"
//#include "SettingHandle.h"
//#include "Profile.h"
//class QScriptEngineDebugger;
class ScriptContextQtWrapper;
class ScriptEngineQtScript;
class ScriptManager;
using ScriptEngineQtScriptPointer = QSharedPointer<ScriptEngineQtScript>;
using ScriptContextQtPointer = QSharedPointer<ScriptContextQtWrapper>;
Q_DECLARE_METATYPE(ScriptEngineQtScriptPointer)
@ -92,18 +69,34 @@ Q_DECLARE_METATYPE(ScriptEngineQtScriptPointer)
* <em>Read-only.</em>
* @property {Script.ResourceBuckets} ExternalPaths - External resource buckets.
*/
class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine {
class ScriptEngineQtScript : public QScriptEngine, public ScriptEngine, public QEnableSharedFromThis<ScriptEngineQtScript> {
Q_OBJECT
using Base = BaseScriptEngine;
public: // ScriptEngine implementation
virtual void abortEvaluation();
virtual void clearExceptions();
virtual ScriptValuePointer cloneUncaughtException(const QString& detail = QString());
virtual ScriptContext* currentContext() const;
//virtual ScriptValuePointer evaluate(const QString& program, const QString& fileName = QString());
//virtual ScriptValuePointer evaluate(const ScriptProgramPointer &program);
//virtual ScriptValuePointer evaluateInClosure(const ScriptValuePointer& locals, const ScriptProgramPointer& program);
virtual ScriptValuePointer globalObject() const;
virtual bool hasUncaughtException() const;
virtual bool isEvaluating() const;
virtual ScriptValuePointer lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1);
virtual ScriptValuePointer makeError(const ScriptValuePointer& other, const QString& type = "Error");
virtual ScriptManager* manager() const;
// if there is a pending exception and we are at the top level (non-recursive) stack frame, this emits and resets it
virtual bool maybeEmitUncaughtException(const QString& debugHint = QString());
virtual ScriptValuePointer newArray(uint length = 0);
virtual ScriptValuePointer newArrayBuffer(const QByteArray& message);
virtual ScriptValuePointer newFunction(ScriptEngine::FunctionSignature fun, int length = 0);
virtual ScriptValuePointer newObject();
virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName);
virtual ScriptValuePointer newQObject(QObject* obj);
virtual ScriptValuePointer newQObject(QObject *object, ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership,
const ScriptEngine::QObjectWrapOptions &options = ScriptEngine::QObjectWrapOptions());
virtual ScriptValuePointer newValue(bool value);
virtual ScriptValuePointer newValue(int value);
virtual ScriptValuePointer newValue(uint value);
@ -113,8 +106,41 @@ public: // ScriptEngine implementation
virtual ScriptValuePointer newValue(const char* value);
virtual ScriptValuePointer newVariant(const QVariant& value);
virtual ScriptValuePointer nullValue();
virtual bool raiseException(const ScriptValuePointer& exception);
//virtual void registerEnum(const QString& enumName, QMetaEnum newEnum);
//Q_INVOKABLE virtual void registerFunction(const QString& name, ScriptEngine::FunctionSignature fun, int numArguments = -1);
//Q_INVOKABLE virtual void registerFunction(const QString& parent, const QString& name, ScriptEngine::FunctionSignature fun, int numArguments = -1);
//Q_INVOKABLE virtual void registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter, ScriptEngine::FunctionSignature setter, const QString& parent = QString(""));
//virtual void registerGlobalObject(const QString& name, QObject* object);
virtual void setDefaultPrototype(int metaTypeId, const ScriptValuePointer& prototype);
virtual void setObjectName(const QString& name);
virtual bool setProperty(const char* name, const QVariant& value);
virtual void setProcessEventsInterval(int interval);
virtual QThread* thread() const;
virtual ScriptValuePointer undefinedValue();
virtual ScriptValuePointer uncaughtException() const;
virtual QStringList uncaughtExceptionBacktrace() const;
virtual int uncaughtExceptionLineNumber() const;
// helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways
inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); }
protected: // brought over from BaseScriptEngine
/**jsdoc
* @function Script.makeError
* @param {object} [other] - Other.
* @param {string} [type="Error"] - Error.
* @returns {object} Object.
* @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error");
// if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it.
// note: this is used in cases where C++ code might call into JS API methods directly
bool raiseException(const QScriptValue& exception);
// helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways
static bool IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method);
public:
ScriptEngineQtScript(ScriptManager* scriptManager = nullptr);
@ -131,7 +157,7 @@ public:
* @deprecated This function is deprecated and will be removed.
*/
/// registers a global object by name
Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object);
Q_INVOKABLE virtual void registerGlobalObject(const QString& name, QObject* object);
/**jsdoc
* @function Script.registerGetterSetter
@ -145,6 +171,8 @@ public:
Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, const QString& parent = QString(""));
Q_INVOKABLE virtual void registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter, ScriptEngine::FunctionSignature setter, const QString& parent = QString(""));
/**jsdoc
* @function Script.registerFunction
* @param {string} name - Name.
@ -155,6 +183,9 @@ public:
/// register a global function
Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1);
Q_INVOKABLE virtual void registerFunction(const QString& name, ScriptEngine::FunctionSignature fun, int numArguments = -1);
/**jsdoc
* @function Script.registerFunction
* @param {string} parent - Parent.
@ -167,6 +198,9 @@ public:
Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun,
int numArguments = -1);
Q_INVOKABLE virtual void registerFunction(const QString& parent, const QString& name, ScriptEngine::FunctionSignature fun, int numArguments = -1);
/**jsdoc
* @function Script.registerEnum
* @param {string} name - Name.
@ -176,7 +210,7 @@ public:
// WARNING: This function must be called after a registerGlobalObject that creates the namespace this enum is located in, or
// the globalObject won't function. E.g., if you have a Foo object and a Foo.FooType enum, Foo must be registered first.
/// registers a global enum
Q_INVOKABLE void registerEnum(const QString& enumName, QMetaEnum newEnum);
Q_INVOKABLE virtual void registerEnum(const QString& enumName, QMetaEnum newEnum);
/**jsdoc
* @function Script.registerValue
@ -196,7 +230,10 @@ public:
* @deprecated This function is deprecated and will be removed.
*/
/// evaluate some code in the context of the ScriptEngineQtScript and return the result
Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget
Q_INVOKABLE virtual ScriptValuePointer evaluate(const QString& program, const QString& fileName); // this is also used by the script tool widget
Q_INVOKABLE virtual ScriptValuePointer evaluate(const ScriptProgramPointer& program);
/**jsdoc
* @function Script.evaluateInClosure
@ -205,7 +242,7 @@ public:
* @returns {object} Object.
* @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program);
Q_INVOKABLE virtual ScriptValuePointer evaluateInClosure(const ScriptValuePointer& locals, const ScriptProgramPointer& program);
/**jsdoc
* Checks whether the application was compiled as a debug build.
@ -245,14 +282,12 @@ public:
// NOTE - this is used by the TypedArray implementation. we need to review this for thread safety
ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
public slots:
/**jsdoc
* @function Script.updateMemoryCost
* @param {number} deltaSize - Delta size.
* @deprecated This function is deprecated and will be removed.
*/
void updateMemoryCost(const qint64&);
virtual void updateMemoryCost(const qint64& deltaSize);
signals:
@ -372,6 +407,20 @@ signals:
// script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example)
void entityScriptDetailsUpdated();
public: // not for public use, but I don't like how Qt strings this along with private friend functions
virtual ScriptValuePointer create(int type, const void* ptr);
virtual bool convert(const ScriptValuePointer& value, int type, void* ptr);
virtual void registerCustomType(int type, ScriptEngine::MarshalFunction mf, ScriptEngine::DemarshalFunction df, const ScriptValuePointer& prototype);
protected:
// like `newFunction`, but allows mapping inline C++ lambdas with captures as callable QScriptValues
// even though the context/engine parameters are redundant in most cases, the function signature matches `newFunction`
// anyway so that newLambdaFunction can be used to rapidly prototype / test utility APIs and then if becoming
// permanent more easily promoted into regular static newFunction scenarios.
QScriptValue newLambdaFunction(std::function<QScriptValue(QScriptContext* context, ScriptEngineQtScript* engine)> operation,
const QScriptValue& data = QScriptValue(),
const QScriptEngine::ValueOwnership& ownership = QScriptEngine::AutoOwnership);
protected:
/**jsdoc
@ -384,6 +433,10 @@ protected:
QPointer<ScriptManager> _manager;
ScriptValuePointer _nullValue;
ScriptValuePointer _undefinedValue;
mutable ScriptContextQtPointer _currContext;
std::atomic<bool> _isRunning { false };
bool _isThreaded { false };
@ -394,4 +447,23 @@ protected:
ArrayBufferClass* _arrayBufferClass;
};
#endif // hifi_ScriptEngineQtScript_h
// Lambda helps create callable QScriptValues out of std::functions:
// (just meant for use from within the script engine itself)
class Lambda : public QObject {
Q_OBJECT
public:
Lambda(ScriptEngineQtScript* engine,
std::function<QScriptValue(QScriptContext* context, ScriptEngineQtScript* engine)> operation,
QScriptValue data);
~Lambda();
public slots:
QScriptValue call();
QString toString() const;
private:
ScriptEngineQtScript* engine;
std::function<QScriptValue(QScriptContext* context, ScriptEngineQtScript* engine)> operation;
QScriptValue data;
};
#endif // hifi_ScriptEngineQtScript_h

View file

@ -0,0 +1,55 @@
//
// ScriptProgramQtWrapper.cpp
// libraries/script-engine/src
//
// Created by Heather Anderson on 8/24/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptProgramQtWrapper.h"
#include <QtScript/QScriptContext>
#include "ScriptEngineQtScript.h"
#include "ScriptValueQtWrapper.h"
ScriptProgramQtWrapper* ScriptProgramQtWrapper::unwrap(ScriptProgramPointer val) {
if (!val) {
return nullptr;
}
return dynamic_cast<ScriptProgramQtWrapper*>(val.get());
}
ScriptSyntaxCheckResultPointer ScriptProgramQtWrapper::checkSyntax() const {
QScriptSyntaxCheckResult result = _engine->checkSyntax(_value.sourceCode());
return ScriptSyntaxCheckResultPointer(new ScriptSyntaxCheckResultQtWrapper(std::move(result)));
}
QString ScriptProgramQtWrapper::fileName() const {
return _value.fileName();
}
QString ScriptProgramQtWrapper::sourceCode() const {
return _value.sourceCode();
}
int ScriptSyntaxCheckResultQtWrapper::errorColumnNumber() const {
return _value.errorColumnNumber();
}
int ScriptSyntaxCheckResultQtWrapper::errorLineNumber() const {
return _value.errorLineNumber();
}
QString ScriptSyntaxCheckResultQtWrapper::errorMessage() const {
return _value.errorMessage();
}
ScriptSyntaxCheckResult::State ScriptSyntaxCheckResultQtWrapper::state() const {
return static_cast<ScriptSyntaxCheckResult::State>(_value.state());
}

View file

@ -0,0 +1,55 @@
//
// ScriptProgramQtWrapper.h
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/21/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ScriptProgramQtWrapper_h
#define hifi_ScriptProgramQtWrapper_h
#include <QtCore/QPointer>
#include <QtScript/QScriptProgram>
#include "../ScriptProgram.h"
#include "ScriptEngineQtScript.h"
class ScriptProgramQtWrapper : public ScriptProgram {
public: // construction
inline ScriptProgramQtWrapper(ScriptEngineQtScript* engine, const QScriptProgram& value) :
_engine(engine), _value(value) {}
inline ScriptProgramQtWrapper(ScriptEngineQtScript* engine, QScriptProgram&& value) :
_engine(engine), _value(std::move(value)) {}
static ScriptProgramQtWrapper* unwrap(ScriptProgramPointer val);
inline const QScriptProgram& toQtValue() const { return _value; }
public: // ScriptProgram implementation
virtual ScriptSyntaxCheckResultPointer checkSyntax() const;
virtual QString fileName() const;
virtual QString sourceCode() const;
private: // storage
QPointer<ScriptEngineQtScript> _engine;
QScriptProgram _value;
};
class ScriptSyntaxCheckResultQtWrapper : public ScriptSyntaxCheckResult {
public: // construction
inline ScriptSyntaxCheckResultQtWrapper(QScriptSyntaxCheckResult&& value) :
_value(std::move(value)) {}
public: // ScriptSyntaxCheckResult implementation
virtual int errorColumnNumber() const;
virtual int errorLineNumber() const;
virtual QString errorMessage() const;
virtual State state() const;
private: // storage
QScriptSyntaxCheckResult _value;
};
#endif // hifi_ScriptValueQtWrapper_h

View file

@ -0,0 +1,33 @@
//
// ScriptValueIteratorQtWrapper.cpp
// libraries/script-engine/src
//
// Created by Heather Anderson on 8/29/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptValueIteratorQtWrapper.h"
ScriptValue::PropertyFlags ScriptValueIteratorQtWrapper::flags() const {
return (ScriptValue::PropertyFlags)(int)_value.flags();
}
bool ScriptValueIteratorQtWrapper::hasNext() const {
return _value.hasNext();
}
QString ScriptValueIteratorQtWrapper::name() const {
return _value.name();
}
void ScriptValueIteratorQtWrapper::next() {
_value.next();
}
ScriptValuePointer ScriptValueIteratorQtWrapper::value() const {
QScriptValue result = _value.value();
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}

View file

@ -0,0 +1,41 @@
//
// ScriptValueIteratorQtWrapper.h
// libraries/script-engine/src
//
// Created by Heather Anderson on 8/29/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ScriptValueIteratorQtWrapper_h
#define hifi_ScriptValueIteratorQtWrapper_h
//#include <QtCore/QPointer>
#include <QtScript/QScriptValueIterator>
#include "../ScriptValueIterator.h"
#include "ScriptEngineQtScript.h"
#include "ScriptValueQtWrapper.h"
class ScriptValueIteratorQtWrapper : public ScriptValueIterator {
public: // construction
inline ScriptValueIteratorQtWrapper(ScriptEngineQtScript* engine, const ScriptValuePointer& object) :
_engine(engine), _value(ScriptValueQtWrapper::fullUnwrap(engine, object)) {}
inline ScriptValueIteratorQtWrapper(ScriptEngineQtScript* engine, const QScriptValue& object) :
_engine(engine), _value(object) {}
public: // ScriptValueIterator implementation
virtual ScriptValue::PropertyFlags flags() const;
virtual bool hasNext() const;
virtual QString name() const;
virtual void next();
virtual ScriptValuePointer value() const;
private: // storage
QPointer<ScriptEngineQtScript> _engine;
QScriptValueIterator _value;
};
#endif // hifi_ScriptValueIteratorQtWrapper_h

View file

@ -0,0 +1,216 @@
//
// ScriptValueQtWrapper.cpp
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/16/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptValueQtWrapper.h"
#include "ScriptValueIteratorQtWrapper.h"
ScriptValueQtWrapper* ScriptValueQtWrapper::unwrap(ScriptValuePointer val) {
if (!val) {
return nullptr;
}
return dynamic_cast<ScriptValueQtWrapper*>(val.data());
}
QScriptValue ScriptValueQtWrapper::fullUnwrap(const ScriptValuePointer& value) const {
if (!value) {
return QScriptValue();
}
ScriptValueQtWrapper* unwrapped = unwrap(value);
if (unwrapped) {
return unwrapped->toQtValue();
}
QVariant varValue = value->toVariant();
return static_cast<QScriptEngine*>(_engine)->newVariant(varValue);
}
QScriptValue ScriptValueQtWrapper::fullUnwrap(ScriptEngineQtScript* engine, const ScriptValuePointer& value) {
if (!value) {
return QScriptValue();
}
ScriptValueQtWrapper* unwrapped = unwrap(value);
if (unwrapped) {
return unwrapped->toQtValue();
}
QVariant varValue = value->toVariant();
return static_cast<QScriptEngine*>(engine)->newVariant(varValue);
}
ScriptValuePointer ScriptValueQtWrapper::call(const ScriptValuePointer& thisObject, const ScriptValueList& args) {
QScriptValue qThis = fullUnwrap(thisObject);
QScriptValueList qArgs;
for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) {
qArgs.push_back(fullUnwrap(*iter));
}
QScriptValue result = _value.call(qThis, qArgs);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptValuePointer ScriptValueQtWrapper::call(const ScriptValuePointer& thisObject, const ScriptValuePointer& arguments) {
QScriptValue qThis = fullUnwrap(thisObject);
QScriptValue qArgs = fullUnwrap(arguments);
QScriptValue result = _value.call(qThis, qArgs);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptValuePointer ScriptValueQtWrapper::construct(const ScriptValueList& args) {
QScriptValueList qArgs;
for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) {
qArgs.push_back(fullUnwrap(*iter));
}
QScriptValue result = _value.construct(qArgs);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptValuePointer ScriptValueQtWrapper::construct(const ScriptValuePointer& arguments) {
QScriptValue unwrapped = fullUnwrap(arguments);
QScriptValue result = _value.construct(unwrapped);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptValuePointer ScriptValueQtWrapper::data() const {
QScriptValue result = _value.data();
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptEnginePointer ScriptValueQtWrapper::engine() const {
if (!_engine) {
return ScriptEnginePointer();
}
return _engine->sharedFromThis();
}
ScriptValueIteratorPointer ScriptValueQtWrapper::newIterator() {
return ScriptValueIteratorPointer(new ScriptValueIteratorQtWrapper(_engine, _value));
}
ScriptValuePointer ScriptValueQtWrapper::property(const QString& name, const ResolveFlags& mode) const {
QScriptValue result = _value.property(name, (QScriptValue::ResolveFlags)(int)mode);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
ScriptValuePointer ScriptValueQtWrapper::property(quint32 arrayIndex, const ResolveFlags& mode) const {
QScriptValue result = _value.property(arrayIndex, (QScriptValue::ResolveFlags)(int)mode);
return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result)));
}
void ScriptValueQtWrapper::setData(const ScriptValuePointer& value) {
QScriptValue unwrapped = fullUnwrap(value);
_value.setData(unwrapped);
}
void ScriptValueQtWrapper::setProperty(const QString& name, const ScriptValuePointer& value, const PropertyFlags& flags) {
QScriptValue unwrapped = fullUnwrap(value);
_value.setProperty(name, unwrapped, (QScriptValue::PropertyFlags)(int)flags);
}
void ScriptValueQtWrapper::setProperty(quint32 arrayIndex, const ScriptValuePointer& value, const PropertyFlags& flags) {
QScriptValue unwrapped = fullUnwrap(value);
_value.setProperty(arrayIndex, unwrapped, (QScriptValue::PropertyFlags)(int)flags);
}
void ScriptValueQtWrapper::setPrototype(const ScriptValuePointer& prototype) {
ScriptValueQtWrapper* unwrappedPrototype = unwrap(prototype);
if (unwrappedPrototype) {
_value.setPrototype(unwrappedPrototype->toQtValue());
}
}
bool ScriptValueQtWrapper::strictlyEquals(const ScriptValuePointer& other) const {
ScriptValueQtWrapper* unwrappedOther = unwrap(other);
return unwrappedOther ? _value.strictlyEquals(unwrappedOther->toQtValue()) : false;
}
bool ScriptValueQtWrapper::toBool() const {
return _value.toBool();
}
qint32 ScriptValueQtWrapper::toInt32() const {
return _value.toInt32();
}
double ScriptValueQtWrapper::toInteger() const {
return _value.toInteger();
}
double ScriptValueQtWrapper::toNumber() const {
return _value.toNumber();
}
QString ScriptValueQtWrapper::toString() const {
return _value.toString();
}
quint16 ScriptValueQtWrapper::toUInt16() const {
return _value.toUInt16();
}
quint32 ScriptValueQtWrapper::toUInt32() const {
return _value.toUInt32();
}
QVariant ScriptValueQtWrapper::toVariant() const {
return _value.toVariant();
}
QObject* ScriptValueQtWrapper::toQObject() const {
return _value.toQObject();
}
bool ScriptValueQtWrapper::equalsInternal(const ScriptValuePointer& other) const {
ScriptValueQtWrapper* unwrappedOther = unwrap(other);
return unwrappedOther ? _value.equals(unwrappedOther->toQtValue()) : false;
}
bool ScriptValueQtWrapper::isArrayInternal() const {
return _value.isArray();
}
bool ScriptValueQtWrapper::isBoolInternal() const {
return _value.isBool();
}
bool ScriptValueQtWrapper::isErrorInternal() const {
return _value.isError();
}
bool ScriptValueQtWrapper::isFunctionInternal() const {
return _value.isFunction();
}
bool ScriptValueQtWrapper::isNumberInternal() const {
return _value.isNumber();
}
bool ScriptValueQtWrapper::isNullInternal() const {
return _value.isNull();
}
bool ScriptValueQtWrapper::isObjectInternal() const {
return _value.isObject();
}
bool ScriptValueQtWrapper::isStringInternal() const {
return _value.isString();
}
bool ScriptValueQtWrapper::isUndefinedInternal() const {
return _value.isUndefined();
}
bool ScriptValueQtWrapper::isValidInternal() const {
return _value.isValid();
}
bool ScriptValueQtWrapper::isVariantInternal() const {
return _value.isVariant();
}

View file

@ -0,0 +1,86 @@
//
// ScriptValueQtWrapper.h
// libraries/script-engine/src
//
// Created by Heather Anderson on 5/16/21.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ScriptValueQtWrapper_h
#define hifi_ScriptValueQtWrapper_h
#include <QtCore/QPointer>
#include <QtScript/QScriptValue>
#include <utility>
#include "../ScriptValue.h"
#include "ScriptEngineQtScript.h"
class ScriptValueQtWrapper : public ScriptValue {
public: // construction
inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, const QScriptValue& value) :
_engine(engine), _value(value) {}
inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, QScriptValue&& value) :
_engine(engine), _value(std::move(value)) {}
static ScriptValueQtWrapper* unwrap(ScriptValuePointer val);
inline const QScriptValue& toQtValue() const { return _value; }
static QScriptValue fullUnwrap(ScriptEngineQtScript* engine, const ScriptValuePointer& value);
public: // ScriptValue implementation
virtual ScriptValuePointer call(const ScriptValuePointer& thisObject = ScriptValuePointer(),
const ScriptValueList& args = ScriptValueList());
virtual ScriptValuePointer call(const ScriptValuePointer& thisObject, const ScriptValuePointer& arguments);
virtual ScriptValuePointer construct(const ScriptValueList& args = ScriptValueList());
virtual ScriptValuePointer construct(const ScriptValuePointer& arguments);
virtual ScriptValuePointer data() const;
virtual ScriptEnginePointer engine() const;
virtual ScriptValueIteratorPointer newIterator();
virtual ScriptValuePointer property(const QString& name, const ResolveFlags& mode = ResolvePrototype) const;
virtual ScriptValuePointer property(quint32 arrayIndex, const ResolveFlags& mode = ResolvePrototype) const;
virtual void setData(const ScriptValuePointer& val);
virtual void setProperty(const QString& name,
const ScriptValuePointer& value,
const PropertyFlags& flags = KeepExistingFlags);
virtual void setProperty(quint32 arrayIndex,
const ScriptValuePointer& value,
const PropertyFlags& flags = KeepExistingFlags);
virtual void setPrototype(const ScriptValuePointer& prototype);
virtual bool strictlyEquals(const ScriptValuePointer& other) const;
virtual bool toBool() const;
virtual qint32 toInt32() const;
virtual double toInteger() const;
virtual double toNumber() const;
virtual QString toString() const;
virtual quint16 toUInt16() const;
virtual quint32 toUInt32() const;
virtual QVariant toVariant() const;
virtual QObject* toQObject() const;
protected: // ScriptValue implementation
virtual bool equalsInternal(const ScriptValuePointer& other) const;
virtual bool isArrayInternal() const;
virtual bool isBoolInternal() const;
virtual bool isErrorInternal() const;
virtual bool isFunctionInternal() const;
virtual bool isNumberInternal() const;
virtual bool isNullInternal() const;
virtual bool isObjectInternal() const;
virtual bool isStringInternal() const;
virtual bool isUndefinedInternal() const;
virtual bool isValidInternal() const;
virtual bool isVariantInternal() const;
private: // helper functions
QScriptValue fullUnwrap(const ScriptValuePointer& value) const;
private: // storage
QPointer<ScriptEngineQtScript> _engine;
QScriptValue _value;
};
#endif // hifi_ScriptValueQtWrapper_h