History

Continued tightening and testing of the scripting engine "glue" logic - QObject wrappers will now be reused when asked to wrap the same QObject multiple times - improved error reporting when we can't form a native call from script - dropped support for having the existing QtScript logic handle Qt object transformations - assuming that our code will handle all calls into native objects - changing timer functions to use CTimer* publicly rather than casted QObject*

Heather Anderson committed 4 years ago

Commit 55b5a2c1 parent 742fa2211 files · +262 −382Browse files

Showing the changes to libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp (1 of 11 files). Show all

Unified · Split

libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp+134−28
@@ -53,6 +53,18 @@ private: // storage · 52 unmodified lines
5353 QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object,
5454 ScriptEngine::ValueOwnership ownership,
5555 const ScriptEngine::QObjectWrapOptions& options) {
56+ QScriptEngine* qengine = static_cast<QScriptEngine*>(engine);
57+
58+ // do we already have a valid wrapper for this QObject?
59+ {
60+ QMutexLocker guard(&engine->_qobjectWrapperMapProtect);
61+ ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);
62+ if (lookup != engine->_qobjectWrapperMap.end()) {
63+ QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock();
64+ if (proxy) return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;
65+ }
66+ }
67+
5668 bool ownsObject;
5769 switch (ownership) {
5870 case ScriptEngine::QtOwnership:
@@ -65,8 +77,33 @@ QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObje · 6 unmodified lines
6577 ownsObject = !object->parent();
6678 break;
6779 }
68− QScriptEngine* qengine = static_cast<QScriptEngine*>(engine);
80+
81+ // create the wrapper
6982 auto proxy = QSharedPointer<ScriptObjectQtProxy>::create(engine, object, ownsObject, options);
83+
84+ {
85+ QMutexLocker guard(&engine->_qobjectWrapperMapProtect);
86+
87+ // check again to see if someone else created the wrapper while we were busy
88+ ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);
89+ if (lookup != engine->_qobjectWrapperMap.end()) {
90+ QSharedPointer<ScriptObjectQtProxy> proxy = lookup.value().lock();
91+ if (proxy) return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;
92+ }
93+
94+ // register the wrapper with the engine and make sure it cleans itself up
95+ engine->_qobjectWrapperMap.insert(object, proxy);
96+ QPointer<ScriptEngineQtScript> enginePtr = engine;
97+ object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() {
98+ if (!enginePtr) return;
99+ QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect);
100+ ScriptEngineQtScript::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object);
101+ if (lookup != enginePtr->_qobjectWrapperMap.end()) {
102+ enginePtr->_qobjectWrapperMap.erase(lookup);
103+ }
104+ });
105+ }
106+
70107 return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));
71108 }
72109
@@ -96,7 +133,6 @@ void ScriptObjectQtProxy::investigate() { · 23 unmodified lines
96133 if (!qobject) return;
97134
98135 const QMetaObject* metaObject = qobject->metaObject();
99− _name = QString::fromLatin1(metaObject->className());
100136
101137 // discover properties
102138 int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0;
@@ -185,6 +221,15 @@ void ScriptObjectQtProxy::investigate() { · 82 unmodified lines
185221 }
186222 }
187223
224+QString ScriptObjectQtProxy::name() const {
225+ Q_ASSERT(_object);
226+ if (!_object) return "";
227+ return _object ? _object->objectName() : "";
228+ QString objectName = _object->objectName();
229+ if (!objectName.isEmpty()) return objectName;
230+ return _object->metaObject()->className();
231+}
232+
188233 QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) {
189234 // check for properties
190235 for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) {
@@ -268,7 +313,7 @@ QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QSc · 77 unmodified lines
268313 MethodDefMap::const_iterator lookup = _methods.find(methodId);
269314 if (lookup == _methods.cend()) return QScriptValue();
270315 return static_cast<QScriptEngine*>(_engine)->newObject(
271− new ScriptMethodQtProxy(_engine, qobject, object, name, lookup.value().methods));
316+ new ScriptMethodQtProxy(_engine, qobject, object, lookup.value().methods));
272317 }
273318 case SIGNAL_TYPE: {
274319 int signalId = id & ~TYPE_MASK;
@@ -278,7 +323,7 @@ QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QSc · 3 unmodified lines
278323 InstanceMap::const_iterator instLookup = _signalInstances.find(signalId);
279324 if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) {
280325 instLookup = _signalInstances.insert(signalId,
281− new ScriptSignalQtProxy(_engine, qobject, object, name, defLookup.value().signal));
326+ new ScriptSignalQtProxy(_engine, qobject, object, defLookup.value().signal));
282327 Q_ASSERT(instLookup != _signalInstances.cend());
283328 }
284329 ScriptSignalQtProxy* proxy = instLookup.value();
@@ -315,10 +360,11 @@ void ScriptObjectQtProxy::setProperty(QScriptValue& object, const QScriptString& · 30 unmodified lines
315360 ScriptContextGuard guard(&ourContext);
316361
317362 int propTypeId = prop.userType();
363+ Q_ASSERT(propTypeId != QMetaType::UnknownType);
318364 QVariant varValue;
319365 if(!_engine->castValueToVariant(value, varValue, propTypeId)) {
320366 QByteArray propTypeName = QMetaType(propTypeId).name();
321− QByteArray valTypeName = value.toVariant().typeName();
367+ QByteArray valTypeName = _engine->valueType(value).toLatin1();
322368 QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext();
323369 currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName));
324370 return;
@@ -352,6 +398,18 @@ QVariant ScriptVariantQtProxy::unwrap(const QScriptValue& val) { · 27 unmodified lines
352398 return proxy ? proxy->toQtValue() : QVariant();
353399 }
354400
401+QString ScriptMethodQtProxy::fullName() const {
402+ Q_ASSERT(_object);
403+ if (!_object) return "";
404+ Q_ASSERT(!_metas.isEmpty());
405+ const QMetaMethod& firstMethod = _metas.front();
406+ QString objectName = _object->objectName();
407+ if (!objectName.isEmpty()) {
408+ return QString("%1.%2").arg(objectName, firstMethod.name());
409+ }
410+ return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name());
411+}
412+
355413 bool ScriptMethodQtProxy::supportsExtension(Extension extension) const {
356414 switch (extension) {
357415 case Callable:
@@ -377,6 +435,9 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 19 unmodified lines
377435
378436 const int scriptValueTypeId = qMetaTypeId<ScriptValue>();
379437
438+ int parameterConversionFailureId = 0;
439+ int parameterConversionFailureCount = 0;
440+
380441 for (auto iter = _metas.cbegin(); iter != _metas.end(); ++iter) {
381442 const QMetaMethod& meta = *iter;
382443 int methodNumArgs = meta.parameterCount();
@@ -387,10 +448,10 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 4 unmodified lines
387448 QList<ScriptValue> qScriptArgList;
388449 QList<QVariant> qVarArgList;
389450 QGenericArgument qGenArgs[10];
390− int arg;
391− bool conversionFailed = false;
392− for (arg = 0; arg < numArgs && !conversionFailed; ++arg) {
451+ int conversionFailures = 0;
452+ for (int arg = 0; arg < numArgs; ++arg) {
393453 int methodArgTypeId = meta.parameterType(arg);
454+ Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
394455 QScriptValue argVal = context->argument(arg);
395456 if (methodArgTypeId == scriptValueTypeId) {
396457 qScriptArgList.append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal)));
@@ -401,24 +462,23 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 4 unmodified lines
401462 } else {
402463 QVariant varArgVal;
403464 if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {
404− conversionFailed = true;
405− break;
406− /*QByteArray methodTypeName = QMetaType(methodArgTypeId).name();
407− QByteArray argTypeName = argVal.toVariant().typeName();
408− context->throwError(QScriptContext::TypeError,
409− QString("Cannot convert %1 to %2").arg(argTypeName, methodTypeName));
410− return QVariant();*/
465+ conversionFailures++;
466+ } else {
467+ qVarArgList.append(varArgVal);
468+ const QVariant& converted = qVarArgList.back();
469+
470+ // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
471+ // A const_cast is needed because calling data() would detach the QVariant.
472+ qGenArgs[arg] =
473+ QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData()));
411474 }
412− qVarArgList.append(varArgVal);
413− const QVariant& converted = qVarArgList.back();
414−
415− // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
416− // A const_cast is needed because calling data() would detach the QVariant.
417− qGenArgs[arg] =
418− QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData()));
419475 }
420476 }
421− if (conversionFailed) {
477+ if (conversionFailures) {
478+ if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) {
479+ parameterConversionFailureCount = conversionFailures;
480+ parameterConversionFailureId = meta.methodIndex();
481+ }
422482 continue;
423483 }
424484
@@ -426,11 +486,18 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 1 unmodified line
426486 ScriptContextGuard guard(&ourContext);
427487
428488 int returnTypeId = meta.returnType();
429− if (returnTypeId == QMetaType::Void) {
489+
490+ // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's
491+ // nothing in there for return values so these need to be explicitly runtime-registered!
492+ Q_ASSERT(returnTypeId != QMetaType::UnknownType);
493+ if (returnTypeId == QMetaType::UnknownType) {
494+ context->throwError(QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()));
495+ return QVariant();
496+ } else if (returnTypeId == QMetaType::Void) {
430497 bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
431498 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
432499 if (!success) {
433− context->throwError("Native call failed");
500+ context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName()));
434501 }
435502 return QVariant();
436503 } else if (returnTypeId == scriptValueTypeId) {
@@ -439,7 +506,7 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 2 unmodified lines
439506 qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6],
440507 qGenArgs[7], qGenArgs[8], qGenArgs[9]);
441508 if (!success) {
442− context->throwError("Native call failed");
509+ context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName()));
443510 return QVariant();
444511 }
445512 QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result);
@@ -454,15 +521,53 @@ QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& arg · 8 unmodified lines
454521 meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
455522 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
456523 if (!success) {
457− context->throwError("Native call failed");
524+ context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName()));
458525 return QVariant();
459526 }
460527 QScriptValue qResult = _engine->castVariantToValue(qRetVal);
461528 return QVariant::fromValue(qResult);
462529 }
463530 }
464− context->throwError("Native call failed: could not locate an overload with the requested arguments");
531+
532+ // we failed to convert the call to C++, try to create a somewhat sane error message
533+ if (parameterConversionFailureCount == 0) {
534+ context->throwError(QString("Native call of %1 failed: unexpected parameter count").arg(fullName()));
535+ return QVariant();
536+ }
537+
538+ const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId);
539+ int methodNumArgs = meta.parameterCount();
540+ Q_ASSERT(methodNumArgs == numArgs);
541+
542+ for (int arg = 0; arg < numArgs; ++arg) {
543+ int methodArgTypeId = meta.parameterType(arg);
544+ Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
545+ QScriptValue argVal = context->argument(arg);
546+ if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) {
547+ QVariant varArgVal;
548+ if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {
549+ QByteArray methodTypeName = QMetaType(methodArgTypeId).name();
550+ QByteArray argTypeName = _engine->valueType(argVal).toLatin1();
551+ context->throwError(QScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4")
552+ .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName));
553+ return QVariant();
554+ }
555+ }
556+ }
557+
558+ Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now?
465559 return QVariant();
560+ context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()));
561+}
562+
563+QString ScriptSignalQtProxy::fullName() const {
564+ Q_ASSERT(_object);
565+ if (!_object) return "";
566+ QString objectName = _object->objectName();
567+ if (!objectName.isEmpty()) {
568+ return QString("%1.%2").arg(objectName, _meta.name());
569+ }
570+ return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name());
466571 }
467572
468573 // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it
@@ -476,6 +581,7 @@ int ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** argu · 7 unmodified lines
476581 int numArgs = _meta.parameterCount();
477582 for (int arg = 0; arg < numArgs; ++arg) {
478583 int methodArgTypeId = _meta.parameterType(arg);
584+ Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
479585 QVariant argValue(methodArgTypeId, arguments[arg+1]);
480586 args.append(_engine->castVariantToValue(argValue));
481587 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t